diff --git a/src/Mvc/.editorconfig b/src/Mvc/.editorconfig new file mode 100644 index 0000000000..abd3d48553 --- /dev/null +++ b/src/Mvc/.editorconfig @@ -0,0 +1,56 @@ +# EditorConfig is awesome:http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Don't use tabs for indentation. +[*] +indent_style = space +# (Please don't specify an indent_size here; that has too many unintended consequences.) + +# Code files +[*.{cs,csx,vb,vbx}] +indent_size = 4 + +# Xml project files +[*.{csproj,vbproj,vcxproj,vcxproj.filters,proj,projitems,shproj}] +indent_size = 2 + +# Xml config files +[*.{props,targets,ruleset,config,nuspec,resx,vsixmanifest,vsct}] +indent_size = 2 + +# JSON files +[*.json] +indent_size = 2 + +# Dotnet code style settings: +[*.cs] +# Sort using and Import directives with System.* appearing first +dotnet_sort_system_directives_first = true + +# Don't use this. qualifier +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion + +# use int x = .. over Int32 +dotnet_style_predefined_type_for_locals_parameters_members = true:suggestion + +# use int.MaxValue over Int32.MaxValue +dotnet_style_predefined_type_for_member_access = true:suggestion + +# Require var all the time. +csharp_style_var_for_built_in_types = true:suggestion +csharp_style_var_when_type_is_apparent = true:suggestion +csharp_style_var_elsewhere = true:suggestion + +# Disallow throw expressions. +csharp_style_throw_expression = false:suggestion + +# Newline settings +csharp_new_line_before_open_brace = all +csharp_new_line_before_else = true +csharp_new_line_before_catch = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_members_in_anonymous_types = true diff --git a/src/Mvc/.gitignore b/src/Mvc/.gitignore new file mode 100644 index 0000000000..e38f0aa101 --- /dev/null +++ b/src/Mvc/.gitignore @@ -0,0 +1,43 @@ +[Oo]bj/ +[Bb]in/ +TestResults/ +.nuget/ +.build/ +.testPublish/ +*.sln.ide/ +_ReSharper.*/ +packages/ +artifacts/ +PublishProfiles/ +.vs/ +bower_components/ +node_modules/ +debugSettings.json +project.lock.json +*.user +*.suo +*.cache +*.docstates +_ReSharper.* +nuget.exe +*net45.csproj +*net451.csproj +*k10.csproj +*.psess +*.vsp +*.pidb +*.userprefs +*DS_Store +*.ncrunchsolution +*.*sdf +*.ipch +.settings +*.sln.ide +node_modules +*launchSettings.json +*.orig +.vscode/ +global.json +BenchmarkDotNet.Artifacts/ +.idea/ +msbuild.binlog diff --git a/src/Mvc/Directory.Build.props b/src/Mvc/Directory.Build.props new file mode 100644 index 0000000000..fad2bb0610 --- /dev/null +++ b/src/Mvc/Directory.Build.props @@ -0,0 +1,21 @@ + + + + + + + + + Microsoft ASP.NET Core MVC + https://github.com/aspnet/Mvc + git + $(MSBuildThisFileDirectory) + $(MSBuildThisFileDirectory)build\Key.snk + true + true + true + + + diff --git a/src/Mvc/Directory.Build.targets b/src/Mvc/Directory.Build.targets new file mode 100644 index 0000000000..eb03b2565f --- /dev/null +++ b/src/Mvc/Directory.Build.targets @@ -0,0 +1,6 @@ + + + $(MicrosoftNETCoreApp21PackageVersion) + $(NETStandardLibrary20PackageVersion) + + diff --git a/src/Mvc/Mvc.NoFun.sln b/src/Mvc/Mvc.NoFun.sln new file mode 100644 index 0000000000..33f4a84d2f --- /dev/null +++ b/src/Mvc/Mvc.NoFun.sln @@ -0,0 +1,595 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2036 +MinimumVisualStudioVersion = 15.0.26730.03 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{32285FA4-6B46-4D6B-A840-2B13E4C8B58E}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}" + ProjectSection(SolutionItems) = preProject + test\Directory.Build.props = test\Directory.Build.props + test\MvcTests.ruleset = test\MvcTests.ruleset + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc", "src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj", "{079EFA1F-0B0A-4853-B27B-5780D111CD85}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Razor", "src\Microsoft.AspNetCore.Mvc.Razor\Microsoft.AspNetCore.Mvc.Razor.csproj", "{314E9AD6-2FFC-4A92-A8AD-510658C64F1E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Core", "src\Microsoft.AspNetCore.Mvc.Core\Microsoft.AspNetCore.Mvc.Core.csproj", "{C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Razor.Test", "test\Microsoft.AspNetCore.Mvc.Razor.Test\Microsoft.AspNetCore.Mvc.Razor.Test.csproj", "{3F6E355E-4869-41D9-943B-D54771221A7F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Core.Test", "test\Microsoft.AspNetCore.Mvc.Core.Test\Microsoft.AspNetCore.Mvc.Core.Test.csproj", "{A8AA326E-8EE8-4F11-B750-23028E0949D7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Test", "test\Microsoft.AspNetCore.Mvc.Test\Microsoft.AspNetCore.Mvc.Test.csproj", "{5F945B82-FE5F-425C-956C-8BC2F2020254}" +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}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.WebApiCompatShimTest", "test\Microsoft.AspNetCore.Mvc.WebApiCompatShimTest\Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj", "{5DE8E4D9-AACD-4B5F-819F-F091383FB996}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.TagHelpers", "src\Microsoft.AspNetCore.Mvc.TagHelpers\Microsoft.AspNetCore.Mvc.TagHelpers.csproj", "{B2347320-308E-4D2B-AEC8-005DFA68B0C9}" +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}" +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 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Abstractions", "src\Microsoft.AspNetCore.Mvc.Abstractions\Microsoft.AspNetCore.Mvc.Abstractions.csproj", "{1154203C-7579-4525-906E-BC55268421C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.ApiExplorer", "src\Microsoft.AspNetCore.Mvc.ApiExplorer\Microsoft.AspNetCore.Mvc.ApiExplorer.csproj", "{A2B72833-5D70-4C42-AE85-E0319926FB8A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.ApiExplorer.Test", "test\Microsoft.AspNetCore.Mvc.ApiExplorer.Test\Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj", "{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Abstractions.Test", "test\Microsoft.AspNetCore.Mvc.Abstractions.Test\Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj", "{DA000953-7532-4DF5-8DB9-8143DF98D999}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.ViewFeatures", "src\Microsoft.AspNetCore.Mvc.ViewFeatures\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj", "{3F8B8FC1-9FE4-4788-8991-367113E8D7AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Formatters.Json", "src\Microsoft.AspNetCore.Mvc.Formatters.Json\Microsoft.AspNetCore.Mvc.Formatters.Json.csproj", "{3FC8D9D6-9352-43A3-8E81-422F270085B7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Formatters.Xml", "src\Microsoft.AspNetCore.Mvc.Formatters.Xml\Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj", "{42C81540-CD47-4C68-A7A3-2A93B9C3B210}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Formatters.Json.Test", "test\Microsoft.AspNetCore.Mvc.Formatters.Json.Test\Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj", "{493780DA-E696-40FF-BD12-4A5C5736F292}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Formatters.Xml.Test", "test\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj", "{22019146-BDFA-442E-8C8E-345FB9644578}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Cors", "src\Microsoft.AspNetCore.Mvc.Cors\Microsoft.AspNetCore.Mvc.Cors.csproj", "{9A07EEA2-942E-4969-9D41-799B6E2D1FF5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.DataAnnotations", "src\Microsoft.AspNetCore.Mvc.DataAnnotations\Microsoft.AspNetCore.Mvc.DataAnnotations.csproj", "{2DD786CA-7AF7-437A-B499-801A589B9A1C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Cors.Test", "test\Microsoft.AspNetCore.Mvc.Cors.Test\Microsoft.AspNetCore.Mvc.Cors.Test.csproj", "{6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.DataAnnotations.Test", "test\Microsoft.AspNetCore.Mvc.DataAnnotations.Test\Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj", "{827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.ViewFeatures.Test", "test\Microsoft.AspNetCore.Mvc.ViewFeatures.Test\Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj", "{60873DFA-97B9-419E-BAA3-940FC9B07085}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Localization", "src\Microsoft.AspNetCore.Mvc.Localization\Microsoft.AspNetCore.Mvc.Localization.csproj", "{50893B10-5735-4F35-9995-F81DA3F0189E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Localization.Test", "test\Microsoft.AspNetCore.Mvc.Localization.Test\Microsoft.AspNetCore.Mvc.Localization.Test.csproj", "{8FC726B5-E766-44E0-8B38-1313B6D8D9A7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.TestDiagnosticListener", "test\Microsoft.AspNetCore.Mvc.TestDiagnosticListener\Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj", "{9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcSandbox", "samples\MvcSandbox\MvcSandbox.csproj", "{14ED4476-9F24-4776-8417-EA6927F6C9C9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.RazorPages", "src\Microsoft.AspNetCore.Mvc.RazorPages\Microsoft.AspNetCore.Mvc.RazorPages.csproj", "{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.RazorPages.Test", "test\Microsoft.AspNetCore.Mvc.RazorPages.Test\Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj", "{0AB46520-F441-4E01-B444-08F4D23F8B1B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Performance", "benchmarks\Microsoft.AspNetCore.Mvc.Performance\Microsoft.AspNetCore.Mvc.Performance.csproj", "{28D4DA20-6E13-47F9-80AE-D6AA7699CC35}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4E1795C3-60C5-4AD2-A15F-93F6CE8FAD36}" + ProjectSection(SolutionItems) = preProject + .appveyor.yml = .appveyor.yml + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + .travis.yml = .travis.yml + build.cmd = build.cmd + build.ps1 = build.ps1 + build.sh = build.sh + CONTRIBUTING.md = CONTRIBUTING.md + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + LICENSE.txt = LICENSE.txt + NuGet.config = NuGet.config + NuGetPackageVerifier.json = NuGetPackageVerifier.json + README.md = README.md + Settings.StyleCop = Settings.StyleCop + version.xml = version.xml + EndProjectSection +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}") = "Microsoft.AspNetCore.Mvc.Analyzers.Test", "test\Microsoft.AspNetCore.Mvc.Analyzers.Test\Microsoft.AspNetCore.Mvc.Analyzers.Test.csproj", "{829D9A67-2D07-4CE6-86C0-59F2549B0CFA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Debug|x86.ActiveCfg = Debug|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Release|Any CPU.Build.0 = Release|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Release|x86.ActiveCfg = Release|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Debug|x86.ActiveCfg = Debug|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Release|Any CPU.Build.0 = Release|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Release|x86.ActiveCfg = Release|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Debug|x86.ActiveCfg = Debug|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|Any CPU.Build.0 = Release|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|x86.ActiveCfg = Release|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Debug|x86.ActiveCfg = Debug|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Release|Any CPU.Build.0 = Release|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Release|x86.ActiveCfg = Release|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Debug|x86.ActiveCfg = Debug|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Release|Any CPU.Build.0 = Release|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Release|x86.ActiveCfg = Release|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|x86.ActiveCfg = Debug|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Any CPU.Build.0 = Release|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|x86.ActiveCfg = Release|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Debug|x86.ActiveCfg = Debug|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Release|Any CPU.Build.0 = Release|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Release|x86.ActiveCfg = Release|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Debug|x86.ActiveCfg = Debug|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|Any CPU.Build.0 = Release|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|x86.ActiveCfg = Release|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Debug|x86.ActiveCfg = Debug|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Any CPU.Build.0 = Release|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|x86.ActiveCfg = Release|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|x86.ActiveCfg = Debug|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Any CPU.Build.0 = Release|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|x86.ActiveCfg = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|x86.ActiveCfg = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|x86.Build.0 = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|Any CPU.Build.0 = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|x86.ActiveCfg = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|x86.Build.0 = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|x86.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|x86.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Any CPU.Build.0 = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|x86.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|x86.Build.0 = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|x86.ActiveCfg = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|x86.Build.0 = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|Any CPU.Build.0 = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|x86.ActiveCfg = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|x86.Build.0 = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|x86.ActiveCfg = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|x86.Build.0 = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|Any CPU.Build.0 = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|x86.ActiveCfg = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|x86.Build.0 = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|x86.Build.0 = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|Any CPU.Build.0 = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|x86.ActiveCfg = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|x86.Build.0 = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|x86.ActiveCfg = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|x86.Build.0 = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Any CPU.Build.0 = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|x86.ActiveCfg = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|x86.Build.0 = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|x86.ActiveCfg = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|x86.Build.0 = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|Any CPU.Build.0 = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|x86.ActiveCfg = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|x86.Build.0 = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|x86.ActiveCfg = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|x86.Build.0 = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|Any CPU.Build.0 = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|x86.ActiveCfg = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|x86.Build.0 = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|x86.ActiveCfg = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|x86.Build.0 = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|Any CPU.Build.0 = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|x86.ActiveCfg = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|x86.Build.0 = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|Any CPU.Build.0 = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|x86.ActiveCfg = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|x86.Build.0 = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|Any CPU.ActiveCfg = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|Any CPU.Build.0 = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|x86.ActiveCfg = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|x86.Build.0 = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|x86.ActiveCfg = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|x86.Build.0 = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|Any CPU.Build.0 = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|x86.ActiveCfg = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|x86.Build.0 = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|x86.ActiveCfg = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|x86.Build.0 = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|Any CPU.Build.0 = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|x86.ActiveCfg = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|x86.Build.0 = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|x86.ActiveCfg = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|x86.Build.0 = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|Any CPU.Build.0 = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|x86.ActiveCfg = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|x86.Build.0 = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|x86.ActiveCfg = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|x86.Build.0 = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|Any CPU.Build.0 = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|x86.ActiveCfg = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|x86.Build.0 = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|x86.ActiveCfg = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|x86.Build.0 = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|Any CPU.Build.0 = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|x86.ActiveCfg = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|x86.Build.0 = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|x86.ActiveCfg = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|x86.Build.0 = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|Any CPU.Build.0 = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|x86.ActiveCfg = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|x86.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|x86.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|x86.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Any CPU.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|x86.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|x86.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|x86.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Any CPU.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|x86.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|x86.Build.0 = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|x86.ActiveCfg = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|x86.Build.0 = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|Any CPU.Build.0 = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|x86.ActiveCfg = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|x86.Build.0 = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|x86.ActiveCfg = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|x86.Build.0 = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|Any CPU.Build.0 = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|x86.ActiveCfg = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|x86.Build.0 = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|x86.ActiveCfg = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|x86.Build.0 = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Any CPU.Build.0 = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|x86.ActiveCfg = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|x86.Build.0 = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|x86.ActiveCfg = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|x86.Build.0 = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Any CPU.Build.0 = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|x86.ActiveCfg = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|x86.Build.0 = Release|Any CPU + {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Debug|x86.ActiveCfg = Debug|Any CPU + {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Debug|x86.Build.0 = Debug|Any CPU + {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Release|Any CPU.Build.0 = Release|Any CPU + {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {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 + {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Debug|x86.ActiveCfg = Debug|Any CPU + {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Debug|x86.Build.0 = Debug|Any CPU + {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Release|Any CPU.Build.0 = Release|Any CPU + {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {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 + {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Debug|x86.ActiveCfg = Debug|Any CPU + {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Debug|x86.Build.0 = Debug|Any CPU + {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Release|Any CPU.Build.0 = Release|Any CPU + {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {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 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {079EFA1F-0B0A-4853-B27B-5780D111CD85} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {3F6E355E-4869-41D9-943B-D54771221A7F} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {A8AA326E-8EE8-4F11-B750-23028E0949D7} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {5F945B82-FE5F-425C-956C-8BC2F2020254} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {5DE8E4D9-AACD-4B5F-819F-F091383FB996} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {B2347320-308E-4D2B-AEC8-005DFA68B0C9} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {860119ED-3DB1-424D-8D0A-30132A8A7D96} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {864FA09D-1E48-403A-A6C8-4F079D2A30F0} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {1154203C-7579-4525-906E-BC55268421C1} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {A2B72833-5D70-4C42-AE85-E0319926FB8A} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {DA000953-7532-4DF5-8DB9-8143DF98D999} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {3FC8D9D6-9352-43A3-8E81-422F270085B7} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {42C81540-CD47-4C68-A7A3-2A93B9C3B210} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {493780DA-E696-40FF-BD12-4A5C5736F292} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {22019146-BDFA-442E-8C8E-345FB9644578} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {2DD786CA-7AF7-437A-B499-801A589B9A1C} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {60873DFA-97B9-419E-BAA3-940FC9B07085} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {50893B10-5735-4F35-9995-F81DA3F0189E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {14ED4476-9F24-4776-8417-EA6927F6C9C9} = {DAAE4C74-D06F-4874-A166-33305D2643CE} + {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} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D003597F-372F-4068-A2F0-353BE3C3B39A} + EndGlobalSection +EndGlobal diff --git a/src/Mvc/Mvc.sln b/src/Mvc/Mvc.sln new file mode 100644 index 0000000000..da45132198 --- /dev/null +++ b/src/Mvc/Mvc.sln @@ -0,0 +1,968 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27130.2036 +MinimumVisualStudioVersion = 15.0.26730.03 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{32285FA4-6B46-4D6B-A840-2B13E4C8B58E}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}" + ProjectSection(SolutionItems) = preProject + test\Directory.Build.props = test\Directory.Build.props + test\MvcTests.ruleset = test\MvcTests.ruleset + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc", "src\Microsoft.AspNetCore.Mvc\Microsoft.AspNetCore.Mvc.csproj", "{079EFA1F-0B0A-4853-B27B-5780D111CD85}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Razor", "src\Microsoft.AspNetCore.Mvc.Razor\Microsoft.AspNetCore.Mvc.Razor.csproj", "{314E9AD6-2FFC-4A92-A8AD-510658C64F1E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Core", "src\Microsoft.AspNetCore.Mvc.Core\Microsoft.AspNetCore.Mvc.Core.csproj", "{C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Razor.Test", "test\Microsoft.AspNetCore.Mvc.Razor.Test\Microsoft.AspNetCore.Mvc.Razor.Test.csproj", "{3F6E355E-4869-41D9-943B-D54771221A7F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Core.Test", "test\Microsoft.AspNetCore.Mvc.Core.Test\Microsoft.AspNetCore.Mvc.Core.Test.csproj", "{A8AA326E-8EE8-4F11-B750-23028E0949D7}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSites", "WebSites", "{16703B76-C9F7-4C75-AE6C-53D92E308E3C}" + ProjectSection(SolutionItems) = preProject + test\WebSites\Directory.Build.props = test\WebSites\Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.FunctionalTests", "test\Microsoft.AspNetCore.Mvc.FunctionalTests\Microsoft.AspNetCore.Mvc.FunctionalTests.csproj", "{323D0C04-B518-4A8F-8A8E-3546AD153D34}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicWebSite", "test\WebSites\BasicWebSite\BasicWebSite.csproj", "{34DF1487-12C6-476C-BE0A-F31DF1939AE5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RoutingWebSite", "test\WebSites\RoutingWebSite\RoutingWebSite.csproj", "{42CDBF4A-E238-4C0F-A416-44588363EB4C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Test", "test\Microsoft.AspNetCore.Mvc.Test\Microsoft.AspNetCore.Mvc.Test.csproj", "{5F945B82-FE5F-425C-956C-8BC2F2020254}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorWebSite", "test\WebSites\RazorWebSite\RazorWebSite.csproj", "{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FormatterWebSite", "test\WebSites\FormatterWebSite\FormatterWebSite.csproj", "{62735776-46FF-4170-9392-02E128A69B89}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApiExplorerWebSite", "test\WebSites\ApiExplorerWebSite\ApiExplorerWebSite.csproj", "{61061528-071E-424E-965A-07BCC2F02672}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VersioningWebSite", "test\WebSites\VersioningWebSite\VersioningWebSite.csproj", "{C6304029-78C8-4604-99BE-2078DCA1DD36}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagHelpersWebSite", "test\WebSites\TagHelpersWebSite\TagHelpersWebSite.csproj", "{6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47}" +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}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApiCompatShimWebSite", "test\WebSites\WebApiCompatShimWebSite\WebApiCompatShimWebSite.csproj", "{B2B7BC91-688E-4C1E-A71F-CE948D958DDF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.WebApiCompatShimTest", "test\Microsoft.AspNetCore.Mvc.WebApiCompatShimTest\Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj", "{5DE8E4D9-AACD-4B5F-819F-F091383FB996}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.TagHelpers", "src\Microsoft.AspNetCore.Mvc.TagHelpers\Microsoft.AspNetCore.Mvc.TagHelpers.csproj", "{B2347320-308E-4D2B-AEC8-005DFA68B0C9}" +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}") = "HtmlGenerationWebSite", "test\WebSites\HtmlGenerationWebSite\HtmlGenerationWebSite.csproj", "{920F8A0E-6F7D-4BBE-84FF-840B89099BE6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ErrorPageMiddlewareWebSite", "test\WebSites\ErrorPageMiddlewareWebSite\ErrorPageMiddlewareWebSite.csproj", "{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XmlFormattersWebSite", "test\WebSites\XmlFormattersWebSite\XmlFormattersWebSite.csproj", "{C3123A70-41C4-4122-AD1C-D35DF8958DD7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControllersFromServicesWebSite", "test\WebSites\ControllersFromServicesWebSite\ControllersFromServicesWebSite.csproj", "{983741B2-4424-4ED1-9B03-7675A67230C8}" +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}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CorsWebSite", "test\WebSites\CorsWebSite\CorsWebSite.csproj", "{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}" +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 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Abstractions", "src\Microsoft.AspNetCore.Mvc.Abstractions\Microsoft.AspNetCore.Mvc.Abstractions.csproj", "{1154203C-7579-4525-906E-BC55268421C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.ApiExplorer", "src\Microsoft.AspNetCore.Mvc.ApiExplorer\Microsoft.AspNetCore.Mvc.ApiExplorer.csproj", "{A2B72833-5D70-4C42-AE85-E0319926FB8A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.ApiExplorer.Test", "test\Microsoft.AspNetCore.Mvc.ApiExplorer.Test\Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj", "{4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Abstractions.Test", "test\Microsoft.AspNetCore.Mvc.Abstractions.Test\Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj", "{DA000953-7532-4DF5-8DB9-8143DF98D999}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.ViewFeatures", "src\Microsoft.AspNetCore.Mvc.ViewFeatures\Microsoft.AspNetCore.Mvc.ViewFeatures.csproj", "{3F8B8FC1-9FE4-4788-8991-367113E8D7AD}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Formatters.Json", "src\Microsoft.AspNetCore.Mvc.Formatters.Json\Microsoft.AspNetCore.Mvc.Formatters.Json.csproj", "{3FC8D9D6-9352-43A3-8E81-422F270085B7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Formatters.Xml", "src\Microsoft.AspNetCore.Mvc.Formatters.Xml\Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj", "{42C81540-CD47-4C68-A7A3-2A93B9C3B210}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Formatters.Json.Test", "test\Microsoft.AspNetCore.Mvc.Formatters.Json.Test\Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj", "{493780DA-E696-40FF-BD12-4A5C5736F292}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Formatters.Xml.Test", "test\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test\Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj", "{22019146-BDFA-442E-8C8E-345FB9644578}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Cors", "src\Microsoft.AspNetCore.Mvc.Cors\Microsoft.AspNetCore.Mvc.Cors.csproj", "{9A07EEA2-942E-4969-9D41-799B6E2D1FF5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.DataAnnotations", "src\Microsoft.AspNetCore.Mvc.DataAnnotations\Microsoft.AspNetCore.Mvc.DataAnnotations.csproj", "{2DD786CA-7AF7-437A-B499-801A589B9A1C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Cors.Test", "test\Microsoft.AspNetCore.Mvc.Cors.Test\Microsoft.AspNetCore.Mvc.Cors.Test.csproj", "{6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.DataAnnotations.Test", "test\Microsoft.AspNetCore.Mvc.DataAnnotations.Test\Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj", "{827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.ViewFeatures.Test", "test\Microsoft.AspNetCore.Mvc.ViewFeatures.Test\Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj", "{60873DFA-97B9-419E-BAA3-940FC9B07085}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Localization", "src\Microsoft.AspNetCore.Mvc.Localization\Microsoft.AspNetCore.Mvc.Localization.csproj", "{50893B10-5735-4F35-9995-F81DA3F0189E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Localization.Test", "test\Microsoft.AspNetCore.Mvc.Localization.Test\Microsoft.AspNetCore.Mvc.Localization.Test.csproj", "{8FC726B5-E766-44E0-8B38-1313B6D8D9A7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.TestDiagnosticListener", "test\Microsoft.AspNetCore.Mvc.TestDiagnosticListener\Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj", "{9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcSandbox", "samples\MvcSandbox\MvcSandbox.csproj", "{14ED4476-9F24-4776-8417-EA6927F6C9C9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SimpleWebSite", "test\WebSites\SimpleWebSite\SimpleWebSite.csproj", "{396B40D7-AC70-49A7-B33C-ED42129FEBE3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SecurityWebSite", "test\WebSites\SecurityWebSite\SecurityWebSite.csproj", "{D28CAC79-7004-4B69-993B-EDEB4653BFA8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.RazorPages", "src\Microsoft.AspNetCore.Mvc.RazorPages\Microsoft.AspNetCore.Mvc.RazorPages.csproj", "{CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.RazorPages.Test", "test\Microsoft.AspNetCore.Mvc.RazorPages.Test\Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj", "{0AB46520-F441-4E01-B444-08F4D23F8B1B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPagesWebSite", "test\WebSites\RazorPagesWebSite\RazorPagesWebSite.csproj", "{4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Performance", "benchmarks\Microsoft.AspNetCore.Mvc.Performance\Microsoft.AspNetCore.Mvc.Performance.csproj", "{F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Testing", "src\Microsoft.AspNetCore.Mvc.Testing\Microsoft.AspNetCore.Mvc.Testing.csproj", "{7500B228-1769-4CFB-A571-3DFAC6678A06}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{631720EE-11CC-41C9-9819-441417ECBEAA}" + ProjectSection(SolutionItems) = preProject + .appveyor.yml = .appveyor.yml + .editorconfig = .editorconfig + .gitattributes = .gitattributes + .gitignore = .gitignore + .travis.yml = .travis.yml + build.cmd = build.cmd + build.ps1 = build.ps1 + build.sh = build.sh + CONTRIBUTING.md = CONTRIBUTING.md + Directory.Build.props = Directory.Build.props + Directory.Build.targets = Directory.Build.targets + LICENSE.txt = LICENSE.txt + NuGet.config = NuGet.config + NuGetPackageVerifier.json = NuGetPackageVerifier.json + README.md = README.md + Settings.StyleCop = Settings.StyleCop + version.xml = version.xml + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{FDC66952-A3EA-4074-899E-C29816BF7C1F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorBuildWebSite", "test\WebSites\RazorBuildWebSite\RazorBuildWebSite.csproj", "{BF8A3392-C3D2-4813-855A-E906564600E1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorBuildWebSite.PrecompiledViews", "test\WebSites\RazorBuildWebSite.PrecompiledViews\RazorBuildWebSite.PrecompiledViews.csproj", "{856D7E25-E033-477D-9ABD-0B50CF428C80}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorBuildWebSite.Views", "test\WebSites\RazorBuildWebSite.Views\RazorBuildWebSite.Views.csproj", "{8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}" +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}" +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 +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|Mixed Platforms = Debug|Mixed Platforms + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|Mixed Platforms = Release|Mixed Platforms + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Debug|Any CPU.Build.0 = Debug|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Debug|x86.ActiveCfg = Debug|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Release|Any CPU.ActiveCfg = Release|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Release|Any CPU.Build.0 = Release|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {079EFA1F-0B0A-4853-B27B-5780D111CD85}.Release|x86.ActiveCfg = Release|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Debug|x86.ActiveCfg = Debug|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Release|Any CPU.Build.0 = Release|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E}.Release|x86.ActiveCfg = Release|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Debug|x86.ActiveCfg = Debug|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|Any CPU.Build.0 = Release|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF}.Release|x86.ActiveCfg = Release|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Debug|x86.ActiveCfg = Debug|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Release|Any CPU.Build.0 = Release|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {3F6E355E-4869-41D9-943B-D54771221A7F}.Release|x86.ActiveCfg = Release|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Debug|x86.ActiveCfg = Debug|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Release|Any CPU.Build.0 = Release|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A8AA326E-8EE8-4F11-B750-23028E0949D7}.Release|x86.ActiveCfg = Release|Any CPU + {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Debug|x86.ActiveCfg = Debug|Any CPU + {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Release|Any CPU.Build.0 = Release|Any CPU + {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Release|x86.ActiveCfg = Release|Any CPU + {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Debug|x86.ActiveCfg = Debug|Any CPU + {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Release|Any CPU.Build.0 = Release|Any CPU + {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Release|x86.ActiveCfg = Release|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|x86.ActiveCfg = Debug|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Any CPU.Build.0 = Release|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|x86.ActiveCfg = Release|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|x86.ActiveCfg = Debug|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Any CPU.Build.0 = Release|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|x86.ActiveCfg = Release|Any CPU + {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|x86.ActiveCfg = Debug|Any CPU + {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Release|Any CPU.Build.0 = Release|Any CPU + {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Release|x86.ActiveCfg = Release|Any CPU + {62735776-46FF-4170-9392-02E128A69B89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {62735776-46FF-4170-9392-02E128A69B89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {62735776-46FF-4170-9392-02E128A69B89}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {62735776-46FF-4170-9392-02E128A69B89}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {62735776-46FF-4170-9392-02E128A69B89}.Debug|x86.ActiveCfg = Debug|Any CPU + {62735776-46FF-4170-9392-02E128A69B89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {62735776-46FF-4170-9392-02E128A69B89}.Release|Any CPU.Build.0 = Release|Any CPU + {62735776-46FF-4170-9392-02E128A69B89}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {62735776-46FF-4170-9392-02E128A69B89}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {62735776-46FF-4170-9392-02E128A69B89}.Release|x86.ActiveCfg = Release|Any CPU + {61061528-071E-424E-965A-07BCC2F02672}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {61061528-071E-424E-965A-07BCC2F02672}.Debug|Any CPU.Build.0 = Debug|Any CPU + {61061528-071E-424E-965A-07BCC2F02672}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {61061528-071E-424E-965A-07BCC2F02672}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {61061528-071E-424E-965A-07BCC2F02672}.Debug|x86.ActiveCfg = Debug|Any CPU + {61061528-071E-424E-965A-07BCC2F02672}.Release|Any CPU.ActiveCfg = Release|Any CPU + {61061528-071E-424E-965A-07BCC2F02672}.Release|Any CPU.Build.0 = Release|Any CPU + {61061528-071E-424E-965A-07BCC2F02672}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {61061528-071E-424E-965A-07BCC2F02672}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {61061528-071E-424E-965A-07BCC2F02672}.Release|x86.ActiveCfg = Release|Any CPU + {C6304029-78C8-4604-99BE-2078DCA1DD36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6304029-78C8-4604-99BE-2078DCA1DD36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6304029-78C8-4604-99BE-2078DCA1DD36}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C6304029-78C8-4604-99BE-2078DCA1DD36}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C6304029-78C8-4604-99BE-2078DCA1DD36}.Debug|x86.ActiveCfg = Debug|Any CPU + {C6304029-78C8-4604-99BE-2078DCA1DD36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6304029-78C8-4604-99BE-2078DCA1DD36}.Release|Any CPU.Build.0 = Release|Any CPU + {C6304029-78C8-4604-99BE-2078DCA1DD36}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C6304029-78C8-4604-99BE-2078DCA1DD36}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C6304029-78C8-4604-99BE-2078DCA1DD36}.Release|x86.ActiveCfg = Release|Any CPU + {6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47}.Debug|x86.ActiveCfg = Debug|Any CPU + {6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47}.Release|Any CPU.Build.0 = Release|Any CPU + {6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47}.Release|x86.ActiveCfg = Release|Any CPU + {0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Debug|x86.ActiveCfg = Debug|Any CPU + {0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Release|Any CPU.Build.0 = Release|Any CPU + {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 + {CAE52CB7-0FAC-4B5B-8251-B0FF837DB657}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {CAE52CB7-0FAC-4B5B-8251-B0FF837DB657}.Debug|x86.ActiveCfg = Debug|Any CPU + {CAE52CB7-0FAC-4B5B-8251-B0FF837DB657}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CAE52CB7-0FAC-4B5B-8251-B0FF837DB657}.Release|Any CPU.Build.0 = Release|Any CPU + {CAE52CB7-0FAC-4B5B-8251-B0FF837DB657}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {CAE52CB7-0FAC-4B5B-8251-B0FF837DB657}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {CAE52CB7-0FAC-4B5B-8251-B0FF837DB657}.Release|x86.ActiveCfg = Release|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Debug|x86.ActiveCfg = Debug|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Release|Any CPU.Build.0 = Release|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {23D30B8C-04B1-4577-A604-ED27EA1E4A0E}.Release|x86.ActiveCfg = Release|Any CPU + {B2B7BC91-688E-4C1E-A71F-CE948D958DDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2B7BC91-688E-4C1E-A71F-CE948D958DDF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2B7BC91-688E-4C1E-A71F-CE948D958DDF}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B2B7BC91-688E-4C1E-A71F-CE948D958DDF}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B2B7BC91-688E-4C1E-A71F-CE948D958DDF}.Debug|x86.ActiveCfg = Debug|Any CPU + {B2B7BC91-688E-4C1E-A71F-CE948D958DDF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2B7BC91-688E-4C1E-A71F-CE948D958DDF}.Release|Any CPU.Build.0 = Release|Any CPU + {B2B7BC91-688E-4C1E-A71F-CE948D958DDF}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B2B7BC91-688E-4C1E-A71F-CE948D958DDF}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B2B7BC91-688E-4C1E-A71F-CE948D958DDF}.Release|x86.ActiveCfg = Release|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Debug|x86.ActiveCfg = Debug|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|Any CPU.Build.0 = Release|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|x86.ActiveCfg = Release|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Debug|x86.ActiveCfg = Debug|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Any CPU.Build.0 = Release|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B2347320-308E-4D2B-AEC8-005DFA68B0C9}.Release|x86.ActiveCfg = Release|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Debug|x86.ActiveCfg = Debug|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Any CPU.Build.0 = Release|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {860119ED-3DB1-424D-8D0A-30132A8A7D96}.Release|x86.ActiveCfg = Release|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Debug|x86.ActiveCfg = Debug|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Debug|x86.Build.0 = Debug|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Release|Any CPU.Build.0 = Release|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Release|x86.ActiveCfg = Release|Any CPU + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6}.Release|x86.Build.0 = Release|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Debug|x86.ActiveCfg = Debug|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Debug|x86.Build.0 = Debug|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|Any CPU.Build.0 = Release|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|x86.ActiveCfg = Release|Any CPU + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}.Release|x86.Build.0 = Release|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Debug|x86.ActiveCfg = Debug|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Debug|x86.Build.0 = Debug|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Release|Any CPU.Build.0 = Release|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Release|x86.ActiveCfg = Release|Any CPU + {C3123A70-41C4-4122-AD1C-D35DF8958DD7}.Release|x86.Build.0 = Release|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Debug|x86.ActiveCfg = Debug|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Debug|x86.Build.0 = Debug|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Release|Any CPU.Build.0 = Release|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Release|x86.ActiveCfg = Release|Any CPU + {983741B2-4424-4ED1-9B03-7675A67230C8}.Release|x86.Build.0 = Release|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Debug|x86.ActiveCfg = Debug|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Debug|x86.Build.0 = Debug|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Release|Any CPU.Build.0 = Release|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Release|x86.ActiveCfg = Release|Any CPU + {551DC89E-2A13-4CF2-83D7-1ADD802443D5}.Release|x86.Build.0 = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|x86.ActiveCfg = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Debug|x86.Build.0 = Debug|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|Any CPU.Build.0 = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|x86.ActiveCfg = Release|Any CPU + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}.Release|x86.Build.0 = Release|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|x86.ActiveCfg = Debug|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Debug|x86.Build.0 = Debug|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|Any CPU.Build.0 = Release|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|x86.ActiveCfg = Release|Any CPU + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D}.Release|x86.Build.0 = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|x86.ActiveCfg = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Debug|x86.Build.0 = Debug|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Any CPU.Build.0 = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|x86.ActiveCfg = Release|Any CPU + {864FA09D-1E48-403A-A6C8-4F079D2A30F0}.Release|x86.Build.0 = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|x86.ActiveCfg = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Debug|x86.Build.0 = Debug|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|Any CPU.Build.0 = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|x86.ActiveCfg = Release|Any CPU + {1154203C-7579-4525-906E-BC55268421C1}.Release|x86.Build.0 = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|x86.ActiveCfg = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Debug|x86.Build.0 = Debug|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|Any CPU.Build.0 = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|x86.ActiveCfg = Release|Any CPU + {A2B72833-5D70-4C42-AE85-E0319926FB8A}.Release|x86.Build.0 = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Debug|x86.Build.0 = Debug|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|Any CPU.Build.0 = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|x86.ActiveCfg = Release|Any CPU + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6}.Release|x86.Build.0 = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|x86.ActiveCfg = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Debug|x86.Build.0 = Debug|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Any CPU.Build.0 = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|x86.ActiveCfg = Release|Any CPU + {DA000953-7532-4DF5-8DB9-8143DF98D999}.Release|x86.Build.0 = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|x86.ActiveCfg = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Debug|x86.Build.0 = Debug|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|Any CPU.Build.0 = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|x86.ActiveCfg = Release|Any CPU + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD}.Release|x86.Build.0 = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|x86.ActiveCfg = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Debug|x86.Build.0 = Debug|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|Any CPU.Build.0 = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|x86.ActiveCfg = Release|Any CPU + {3FC8D9D6-9352-43A3-8E81-422F270085B7}.Release|x86.Build.0 = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|x86.ActiveCfg = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Debug|x86.Build.0 = Debug|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|Any CPU.Build.0 = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|x86.ActiveCfg = Release|Any CPU + {42C81540-CD47-4C68-A7A3-2A93B9C3B210}.Release|x86.Build.0 = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|Any CPU.Build.0 = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|x86.ActiveCfg = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Debug|x86.Build.0 = Debug|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|Any CPU.ActiveCfg = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|Any CPU.Build.0 = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|x86.ActiveCfg = Release|Any CPU + {493780DA-E696-40FF-BD12-4A5C5736F292}.Release|x86.Build.0 = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|x86.ActiveCfg = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Debug|x86.Build.0 = Debug|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|Any CPU.Build.0 = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|x86.ActiveCfg = Release|Any CPU + {22019146-BDFA-442E-8C8E-345FB9644578}.Release|x86.Build.0 = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|x86.ActiveCfg = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Debug|x86.Build.0 = Debug|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|Any CPU.Build.0 = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|x86.ActiveCfg = Release|Any CPU + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5}.Release|x86.Build.0 = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|x86.ActiveCfg = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Debug|x86.Build.0 = Debug|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|Any CPU.Build.0 = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|x86.ActiveCfg = Release|Any CPU + {2DD786CA-7AF7-437A-B499-801A589B9A1C}.Release|x86.Build.0 = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|x86.ActiveCfg = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Debug|x86.Build.0 = Debug|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|Any CPU.Build.0 = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|x86.ActiveCfg = Release|Any CPU + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5}.Release|x86.Build.0 = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|x86.ActiveCfg = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Debug|x86.Build.0 = Debug|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|Any CPU.Build.0 = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|x86.ActiveCfg = Release|Any CPU + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B}.Release|x86.Build.0 = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|Any CPU.Build.0 = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|x86.ActiveCfg = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Debug|x86.Build.0 = Debug|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|Any CPU.ActiveCfg = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|Any CPU.Build.0 = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|x86.ActiveCfg = Release|Any CPU + {60873DFA-97B9-419E-BAA3-940FC9B07085}.Release|x86.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|x86.ActiveCfg = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Debug|x86.Build.0 = Debug|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Any CPU.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|x86.ActiveCfg = Release|Any CPU + {50893B10-5735-4F35-9995-F81DA3F0189E}.Release|x86.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|x86.ActiveCfg = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Debug|x86.Build.0 = Debug|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Any CPU.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|x86.ActiveCfg = Release|Any CPU + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7}.Release|x86.Build.0 = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|x86.ActiveCfg = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Debug|x86.Build.0 = Debug|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|Any CPU.Build.0 = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|x86.ActiveCfg = Release|Any CPU + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4}.Release|x86.Build.0 = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|x86.ActiveCfg = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Debug|x86.Build.0 = Debug|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|Any CPU.Build.0 = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|x86.ActiveCfg = Release|Any CPU + {14ED4476-9F24-4776-8417-EA6927F6C9C9}.Release|x86.Build.0 = Release|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Debug|x86.ActiveCfg = Debug|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Debug|x86.Build.0 = Debug|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Release|Any CPU.Build.0 = Release|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Release|x86.ActiveCfg = Release|Any CPU + {396B40D7-AC70-49A7-B33C-ED42129FEBE3}.Release|x86.Build.0 = Release|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|x86.ActiveCfg = Debug|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Debug|x86.Build.0 = Debug|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|Any CPU.Build.0 = Release|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|x86.ActiveCfg = Release|Any CPU + {D28CAC79-7004-4B69-993B-EDEB4653BFA8}.Release|x86.Build.0 = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|x86.ActiveCfg = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Debug|x86.Build.0 = Debug|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Any CPU.Build.0 = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|x86.ActiveCfg = Release|Any CPU + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53}.Release|x86.Build.0 = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|x86.ActiveCfg = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Debug|x86.Build.0 = Debug|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Any CPU.Build.0 = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|x86.ActiveCfg = Release|Any CPU + {0AB46520-F441-4E01-B444-08F4D23F8B1B}.Release|x86.Build.0 = Release|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Debug|x86.ActiveCfg = Debug|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Debug|x86.Build.0 = Debug|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Release|Any CPU.Build.0 = Release|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Release|x86.ActiveCfg = Release|Any CPU + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352}.Release|x86.Build.0 = Release|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Debug|x86.ActiveCfg = Debug|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Debug|x86.Build.0 = Debug|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Release|Any CPU.Build.0 = Release|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Release|x86.ActiveCfg = Release|Any CPU + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE}.Release|x86.Build.0 = Release|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|x86.ActiveCfg = Debug|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Debug|x86.Build.0 = Debug|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|Any CPU.Build.0 = Release|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|x86.ActiveCfg = Release|Any CPU + {7500B228-1769-4CFB-A571-3DFAC6678A06}.Release|x86.Build.0 = Release|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|x86.ActiveCfg = Debug|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Debug|x86.Build.0 = Debug|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Release|Any CPU.Build.0 = Release|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Release|x86.ActiveCfg = Release|Any CPU + {BF8A3392-C3D2-4813-855A-E906564600E1}.Release|x86.Build.0 = Release|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|Any CPU.Build.0 = Debug|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|x86.ActiveCfg = Debug|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Debug|x86.Build.0 = Debug|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|Any CPU.ActiveCfg = Release|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|Any CPU.Build.0 = Release|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|x86.ActiveCfg = Release|Any CPU + {856D7E25-E033-477D-9ABD-0B50CF428C80}.Release|x86.Build.0 = Release|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|x86.ActiveCfg = Debug|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Debug|x86.Build.0 = Debug|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|Any CPU.Build.0 = Release|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|x86.ActiveCfg = Release|Any CPU + {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A}.Release|x86.Build.0 = Release|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Debug|x86.ActiveCfg = Debug|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Debug|x86.Build.0 = Debug|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Release|Any CPU.Build.0 = Release|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Release|x86.ActiveCfg = Release|Any CPU + {87A3E227-C45E-4141-A59F-402908E651FD}.Release|x86.Build.0 = Release|Any CPU + {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Debug|x86.ActiveCfg = Debug|Any CPU + {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Debug|x86.Build.0 = Debug|Any CPU + {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Release|Any CPU.Build.0 = Release|Any CPU + {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {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 + {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Debug|x86.ActiveCfg = Debug|Any CPU + {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Debug|x86.Build.0 = Debug|Any CPU + {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Release|Any CPU.Build.0 = Release|Any CPU + {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {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 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {079EFA1F-0B0A-4853-B27B-5780D111CD85} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {314E9AD6-2FFC-4A92-A8AD-510658C64F1E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {C48DA9D7-ACB5-4408-AA79-27ECB60A67EF} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {3F6E355E-4869-41D9-943B-D54771221A7F} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {A8AA326E-8EE8-4F11-B750-23028E0949D7} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {16703B76-C9F7-4C75-AE6C-53D92E308E3C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {323D0C04-B518-4A8F-8A8E-3546AD153D34} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {34DF1487-12C6-476C-BE0A-F31DF1939AE5} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {42CDBF4A-E238-4C0F-A416-44588363EB4C} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {5F945B82-FE5F-425C-956C-8BC2F2020254} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {B07CAF59-11ED-40E3-A5DB-E1178F84FA78} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {62735776-46FF-4170-9392-02E128A69B89} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {61061528-071E-424E-965A-07BCC2F02672} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {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} + {5DE8E4D9-AACD-4B5F-819F-F091383FB996} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {B2347320-308E-4D2B-AEC8-005DFA68B0C9} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {860119ED-3DB1-424D-8D0A-30132A8A7D96} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {920F8A0E-6F7D-4BBE-84FF-840B89099BE6} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {AD545A5B-2BA5-4314-88AC-FC2ACF2CC718} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {C3123A70-41C4-4122-AD1C-D35DF8958DD7} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {983741B2-4424-4ED1-9B03-7675A67230C8} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {551DC89E-2A13-4CF2-83D7-1ADD802443D5} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {F504357E-C2E1-4818-BA5C-9A2EAC25FEE5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {94BA134D-04B3-48AA-BA55-5A4DB8640F2D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {864FA09D-1E48-403A-A6C8-4F079D2A30F0} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {1154203C-7579-4525-906E-BC55268421C1} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {A2B72833-5D70-4C42-AE85-E0319926FB8A} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {4C2AD8AB-8AC0-46C4-80C6-C5577C7255F6} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {DA000953-7532-4DF5-8DB9-8143DF98D999} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {3F8B8FC1-9FE4-4788-8991-367113E8D7AD} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {3FC8D9D6-9352-43A3-8E81-422F270085B7} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {42C81540-CD47-4C68-A7A3-2A93B9C3B210} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {493780DA-E696-40FF-BD12-4A5C5736F292} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {22019146-BDFA-442E-8C8E-345FB9644578} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {9A07EEA2-942E-4969-9D41-799B6E2D1FF5} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {2DD786CA-7AF7-437A-B499-801A589B9A1C} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {6BB4C20B-24C0-45D6-9E4C-C2620959BDD5} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {827DBBCB-F3A9-4BAD-8262-4BD43E28EB3B} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {60873DFA-97B9-419E-BAA3-940FC9B07085} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {50893B10-5735-4F35-9995-F81DA3F0189E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {8FC726B5-E766-44E0-8B38-1313B6D8D9A7} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {9879B5D5-2325-4A81-B4DF-F279FE8FEEB4} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {14ED4476-9F24-4776-8417-EA6927F6C9C9} = {DAAE4C74-D06F-4874-A166-33305D2643CE} + {396B40D7-AC70-49A7-B33C-ED42129FEBE3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {D28CAC79-7004-4B69-993B-EDEB4653BFA8} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {0AB46520-F441-4E01-B444-08F4D23F8B1B} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {4BA6EC9A-B6D9-41F2-BFDA-D82B22D80352} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {F16CEE0D-A28E-43BD-802F-99BAFE4BA7CE} = {FDC66952-A3EA-4074-899E-C29816BF7C1F} + {7500B228-1769-4CFB-A571-3DFAC6678A06} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {BF8A3392-C3D2-4813-855A-E906564600E1} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {856D7E25-E033-477D-9ABD-0B50CF428C80} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {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} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A} + EndGlobalSection +EndGlobal diff --git a/src/Mvc/NuGetPackageVerifier.json b/src/Mvc/NuGetPackageVerifier.json new file mode 100644 index 0000000000..b153ab1515 --- /dev/null +++ b/src/Mvc/NuGetPackageVerifier.json @@ -0,0 +1,7 @@ +{ + "Default": { + "rules": [ + "DefaultCompositeRule" + ] + } +} \ No newline at end of file diff --git a/src/Mvc/README.md b/src/Mvc/README.md new file mode 100644 index 0000000000..e5aa14d771 --- /dev/null +++ b/src/Mvc/README.md @@ -0,0 +1,28 @@ +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. + +ASP.NET Core MVC includes support for building web pages and HTTP services in a single aligned framework that can be hosted in IIS or self-hosted in your own process. + +See the [ASP.NET Core MVC documentation](https://docs.microsoft.com/aspnet/core/). + +Related community projects: +* [AspNet.Mvc.TypedRouting](https://github.com/ivaylokenov/AspNet.Mvc.TypedRouting): A collection of extension methods providing strongly typed routing and link generation for ASP.NET Core MVC projects. +* [ASP.NET MVC Boilerplate](https://visualstudiogallery.msdn.microsoft.com/6cf50a48-fc1e-4eaf-9e82-0b2a6705ca7d): Rich templates for ASP.NET Core MVC. +* [MyTested.AspNetCore.Mvc](https://github.com/ivaylokenov/MyTested.AspNetCore.Mvc): Powerful fluent testing framework for ASP.NET Core MVC. +* [MvcDeviceDetector](https://github.com/laskoviymishka/MvcDeviceDetector): Device detection mechanism to create mobile web applications. +* [XmlResult](https://github.com/Wallsmedia/XmlResult): XML formatter extensions to allow defining the XML serializer type. +* [AspNetCoreImageTagHelper](https://github.com/ignatandrei/AspNetCoreImageTagHelper): Tag helper for rendering images as inline base64 data. + +This project is part of ASP.NET Core. You can find samples, documentation and getting started instructions for ASP.NET Core at the [Home](https://github.com/aspnet/home) repo. + +## Building from source + +To run a complete build on command line only, execute `build.cmd` or `build.sh` without arguments. See [developer documentation](https://github.com/aspnet/Home/wiki) for more details. diff --git a/src/Mvc/Settings.StyleCop b/src/Mvc/Settings.StyleCop new file mode 100644 index 0000000000..7b8130298c --- /dev/null +++ b/src/Mvc/Settings.StyleCop @@ -0,0 +1,455 @@ + + + NoMerge + + + + + False + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + as + db + dc + do + ef + id + if + in + is + my + no + on + sl + to + ui + vs + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + True + True + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + False + + + + + + + + + + False + + + + + + + False + + + + + False + + + + + + + + + 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. + + + + + + 120 + + + + \ No newline at end of file diff --git a/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs new file mode 100644 index 0000000000..ec9c2c5a4b --- /dev/null +++ b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.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 System.Linq; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +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.Routing; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.Performance +{ + public class ActionSelectorBenchmark + { + private const int Seed = 1000; + + // About 35 or so plausible sounding conventional routing actions. + // + // We include some duplicates here, because that's what happens when you have one method that handles + // GET and one that handles POST. + private static readonly ActionDescriptor[] _actions = new ActionDescriptor[] + { + CreateActionDescriptor(new { area = "Admin", controller = "Account", action = "AddUser" }), + CreateActionDescriptor(new { area = "Admin", controller = "Account", action = "AddUser" }), + CreateActionDescriptor(new { area = "Admin", controller = "Account", action = "DeleteUser" }), + CreateActionDescriptor(new { area = "Admin", controller = "Account", action = "DeleteUser" }), + CreateActionDescriptor(new { area = "Admin", controller = "Account", action = "Details" }), + CreateActionDescriptor(new { area = "Admin", controller = "Account", action = "List" }), + + CreateActionDescriptor(new { area = "Admin", controller = "Diagnostics", action = "Stats" }), + CreateActionDescriptor(new { area = "Admin", controller = "Diagnostics", action = "Performance" }), + + CreateActionDescriptor(new { area = "Admin", controller = "Products", action = "CreateProduct" }), + CreateActionDescriptor(new { area = "Admin", controller = "Products", action = "CreateProduct" }), + CreateActionDescriptor(new { area = "Admin", controller = "Products", action = "DeleteProduct" }), + CreateActionDescriptor(new { area = "Admin", controller = "Products", action = "DeleteProduct" }), + CreateActionDescriptor(new { area = "Admin", controller = "Products", action = "EditProduct" }), + CreateActionDescriptor(new { area = "Admin", controller = "Products", action = "EditProduct" }), + CreateActionDescriptor(new { area = "Admin", controller = "Products", action = "Index" }), + CreateActionDescriptor(new { area = "Admin", controller = "Products", action = "Inventory" }), + + CreateActionDescriptor(new { area = "Store", controller = "Search", action = "FindProduct" }), + CreateActionDescriptor(new { area = "Store", controller = "Search", action = "ShowCategory" }), + CreateActionDescriptor(new { area = "Store", controller = "Search", action = "HotItems" }), + + CreateActionDescriptor(new { area = "Store", controller = "Product", action = "Index" }), + CreateActionDescriptor(new { area = "Store", controller = "Product", action = "Details" }), + CreateActionDescriptor(new { area = "Store", controller = "Product", action = "Buy" }), + + CreateActionDescriptor(new { area = "Store", controller = "Checkout", action = "ViewCart" }), + CreateActionDescriptor(new { area = "Store", controller = "Checkout", action = "Billing" }), + CreateActionDescriptor(new { area = "Store", controller = "Checkout", action = "Confim" }), + CreateActionDescriptor(new { area = "Store", controller = "Checkout", action = "Confim" }), + + CreateActionDescriptor(new { area = "", controller = "Blog", action = "Index" }), + CreateActionDescriptor(new { area = "", controller = "Blog", action = "Search" }), + CreateActionDescriptor(new { area = "", controller = "Blog", action = "ViewPost" }), + CreateActionDescriptor(new { area = "", controller = "Blog", action = "PostComment" }), + + CreateActionDescriptor(new { area = "", controller = "Home", action = "Index" }), + CreateActionDescriptor(new { area = "", controller = "Home", action = "Search" }), + CreateActionDescriptor(new { area = "", controller = "Home", action = "About" }), + CreateActionDescriptor(new { area = "", controller = "Home", action = "Contact" }), + CreateActionDescriptor(new { area = "", controller = "Home", action = "Support" }), + }; + + private static readonly KeyValuePair>[] _dataSet = GetDataSet(_actions); + + private static readonly IActionSelector _actionSelector = CreateActionSelector(_actions); + + [Benchmark(Description = "conventional action selection implementation")] + public void SelectCandidates_MatchRouteData() + { + var routeContext = new RouteContext(new DefaultHttpContext()); + + for (var i = 0; i < _dataSet.Length; i++) + { + var routeValues = _dataSet[i].Key; + var expected = _dataSet[i].Value; + + var state = routeContext.RouteData.PushState(MockRouter.Instance, routeValues, null); + + var actual = _actionSelector.SelectCandidates(routeContext); + Verify(expected, actual); + + state.Restore(); + } + } + + [Benchmark(Baseline = true, Description = "conventional action selection baseline")] + public void SelectCandidates_Baseline() + { + var routeContext = new RouteContext(new DefaultHttpContext()); + + for (var i = 0; i < _dataSet.Length; i++) + { + var routeValues = _dataSet[i].Key; + var expected = _dataSet[i].Value; + + var state = routeContext.RouteData.PushState(MockRouter.Instance, routeValues, null); + + var actual = NaiveSelectCandiates(_actions, routeContext.RouteData.Values); + Verify(expected, actual); + + state.Restore(); + } + } + + // A naive implementation we can use to generate match data for inputs, and for a baseline. + private static IReadOnlyList NaiveSelectCandiates(ActionDescriptor[] actions, RouteValueDictionary routeValues) + { + var results = new List(); + for (var i = 0; i < actions.Length; i++) + { + var action = actions[i]; + + var isMatch = true; + foreach (var kvp in action.RouteValues) + { + var routeValue = Convert.ToString(routeValues[kvp.Key]) ?? string.Empty; + + if (string.IsNullOrEmpty(kvp.Value) && string.IsNullOrEmpty(routeValue)) + { + // Match + } + else if (string.Equals(kvp.Value, routeValue, StringComparison.OrdinalIgnoreCase)) + { + // Match; + } + else + { + isMatch = false; + break; + } + } + + if (isMatch) + { + results.Add(action); + } + } + + return results; + } + + private static ActionDescriptor CreateActionDescriptor(object obj) + { + // Our real ActionDescriptors don't use RVD, they use a regular old dictionary. + // Just using RVD here to understand the anonymous object for brevity. + var routeValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var kvp in new RouteValueDictionary(obj)) + { + routeValues.Add(kvp.Key, Convert.ToString(kvp.Value) ?? string.Empty); + } + + return new ActionDescriptor() + { + RouteValues = routeValues, + }; + } + + private static KeyValuePair>[] GetDataSet(ActionDescriptor[] actions) + { + var random = new Random(Seed); + + var data = new List>>(); + + for (var i = 0; i < actions.Length; i += 2) + { + var action = actions[i]; + var routeValues = new RouteValueDictionary(action.RouteValues); + var matches = NaiveSelectCandiates(actions, routeValues); + if (matches.Count == 0) + { + throw new InvalidOperationException("This should have at least one match."); + } + + + data.Add(new KeyValuePair>(routeValues, matches)); + } + + for (var i = 1; i < actions.Length; i += 3) + { + var action = actions[i]; + var routeValues = new RouteValueDictionary(action.RouteValues); + + // Make one of the route values not match. + routeValues[routeValues.First().Key] = ((string)routeValues.First().Value) + "fkdkfdkkf"; + + var matches = NaiveSelectCandiates(actions, routeValues); + if (matches.Count != 0) + { + throw new InvalidOperationException("This should have 0 matches."); + } + + data.Add(new KeyValuePair>(routeValues, matches)); + } + + return data.ToArray(); + } + + private static void Verify(IReadOnlyList expected, IReadOnlyList actual) + { + if (expected.Count == 0 && actual == null) + { + return; + } + + if (expected.Count != actual.Count) + { + throw new InvalidOperationException("The count is different."); + } + + for (var i = 0; i < actual.Count; i++) + { + if (!object.ReferenceEquals(expected[i], actual[i])) + { + throw new InvalidOperationException("The actions don't match."); + } + } + } + + private static IActionSelector CreateActionSelector(ActionDescriptor[] actions) + { + var actionCollection = new MockActionDescriptorCollectionProvider(actions); + + return new ActionSelector( + actionCollection, + new ActionConstraintCache(actionCollection, Enumerable.Empty()), + NullLoggerFactory.Instance); + } + + private class MockActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider + { + public MockActionDescriptorCollectionProvider(ActionDescriptor[] actions) + { + ActionDescriptors = new ActionDescriptorCollection(actions, 0); + } + + public ActionDescriptorCollection ActionDescriptors { get; } + } + + private class MockRouter : IRouter + { + public static readonly IRouter Instance = new MockRouter(); + + public VirtualPathData GetVirtualPath(VirtualPathContext context) + { + throw new NotImplementedException(); + } + + public Task RouteAsync(RouteContext context) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/AssemblyInfo.cs b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/AssemblyInfo.cs new file mode 100644 index 0000000000..32248e0d1b --- /dev/null +++ b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/AssemblyInfo.cs @@ -0,0 +1 @@ +[assembly: BenchmarkDotNet.Attributes.AspNetCoreBenchmark] diff --git a/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj new file mode 100644 index 0000000000..da89cbef0a --- /dev/null +++ b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp2.1 + Exe + true + true + + + + + + + + + + + + diff --git a/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/readme.md b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/readme.md new file mode 100644 index 0000000000..8adc74351b --- /dev/null +++ b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/readme.md @@ -0,0 +1,11 @@ +Compile the solution in Release mode (so binaries are available in release) + +To run a specific benchmark add it as parameter +``` +dotnet run -c Release +``` +To run all use `All` as parameter +``` +dotnet run -c Release All +``` +Using no parameter will list all available benchmarks \ No newline at end of file diff --git a/src/Mvc/build/Key.snk b/src/Mvc/build/Key.snk new file mode 100644 index 0000000000..e10e4889c1 Binary files /dev/null and b/src/Mvc/build/Key.snk differ diff --git a/src/Mvc/build/buildpipeline/linux.groovy b/src/Mvc/build/buildpipeline/linux.groovy new file mode 100644 index 0000000000..903f218bb8 --- /dev/null +++ b/src/Mvc/build/buildpipeline/linux.groovy @@ -0,0 +1,10 @@ +@Library('dotnet-ci') _ + +simpleNode('Ubuntu16.04', 'latest-or-auto-docker') { + stage ('Checking out source') { + checkout scm + } + stage ('Build') { + sh './build.sh --ci' + } +} diff --git a/src/Mvc/build/buildpipeline/osx.groovy b/src/Mvc/build/buildpipeline/osx.groovy new file mode 100644 index 0000000000..aaac63686b --- /dev/null +++ b/src/Mvc/build/buildpipeline/osx.groovy @@ -0,0 +1,10 @@ +@Library('dotnet-ci') _ + +simpleNode('OSX10.12','latest') { + stage ('Checking out source') { + checkout scm + } + stage ('Build') { + sh './build.sh --ci' + } +} diff --git a/src/Mvc/build/buildpipeline/pipeline.groovy b/src/Mvc/build/buildpipeline/pipeline.groovy new file mode 100644 index 0000000000..e915cadae1 --- /dev/null +++ b/src/Mvc/build/buildpipeline/pipeline.groovy @@ -0,0 +1,18 @@ +import org.dotnet.ci.pipelines.Pipeline + +def windowsPipeline = Pipeline.createPipeline(this, 'build/buildpipeline/windows.groovy') +def linuxPipeline = Pipeline.createPipeline(this, 'build/buildpipeline/linux.groovy') +def osxPipeline = Pipeline.createPipeline(this, 'build/buildpipeline/osx.groovy') +String configuration = 'Release' +def parameters = [ + 'Configuration': configuration +] + +windowsPipeline.triggerPipelineOnEveryGithubPR("Windows ${configuration} x64 Build", parameters) +windowsPipeline.triggerPipelineOnGithubPush(parameters) + +linuxPipeline.triggerPipelineOnEveryGithubPR("Ubuntu 16.04 ${configuration} Build", parameters) +linuxPipeline.triggerPipelineOnGithubPush(parameters) + +osxPipeline.triggerPipelineOnEveryGithubPR("OSX 10.12 ${configuration} Build", parameters) +osxPipeline.triggerPipelineOnGithubPush(parameters) diff --git a/src/Mvc/build/buildpipeline/windows.groovy b/src/Mvc/build/buildpipeline/windows.groovy new file mode 100644 index 0000000000..8d26f313d4 --- /dev/null +++ b/src/Mvc/build/buildpipeline/windows.groovy @@ -0,0 +1,12 @@ +@Library('dotnet-ci') _ + +// 'node' indicates to Jenkins that the enclosed block runs on a node that matches +// the label 'windows-with-vs' +simpleNode('Windows_NT','latest') { + stage ('Checking out source') { + checkout scm + } + stage ('Build') { + bat '.\\run.cmd -CI default-build' + } +} diff --git a/src/Mvc/build/dependencies.props b/src/Mvc/build/dependencies.props new file mode 100644 index 0000000000..3e21512352 --- /dev/null +++ b/src/Mvc/build/dependencies.props @@ -0,0 +1,103 @@ + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + 0.9.9 + 0.10.13 + 2.1.3-rtm-15802 + 5.2.6 + 2.8.0 + 2.8.0 + 1.7.0 + 2.1.0 + 2.0.0 + 2.1.2 + 2.1.1 + 15.6.1 + 4.7.49 + 2.0.3 + 1.0.1 + 4.5.0 + 4.5.0 + 4.5.1 + 0.8.0 + 2.3.1 + 2.4.0-beta.1.build3945 + + + + + + + + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.2 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.0 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.0 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + 2.1.1 + + diff --git a/src/Mvc/build/repo.props b/src/Mvc/build/repo.props new file mode 100644 index 0000000000..53e1be33f0 --- /dev/null +++ b/src/Mvc/build/repo.props @@ -0,0 +1,23 @@ + + + + + true + + + + + + + + + + Internal.AspNetCore.Universe.Lineup + 2.1.0-rc1-* + https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json + + + + + + diff --git a/src/Mvc/build/sources.props b/src/Mvc/build/sources.props new file mode 100644 index 0000000000..9215df9751 --- /dev/null +++ b/src/Mvc/build/sources.props @@ -0,0 +1,17 @@ + + + + + $(DotNetRestoreSources) + + $(RestoreSources); + https://dotnet.myget.org/F/dotnet-core/api/v3/index.json; + https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json; + https://dotnet.myget.org/F/aspnetcore-tools/api/v3/index.json; + + + $(RestoreSources); + https://api.nuget.org/v3/index.json; + + + diff --git a/src/Mvc/samples/MvcSandbox/.bowerrc b/src/Mvc/samples/MvcSandbox/.bowerrc new file mode 100644 index 0000000000..6406626abf --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/.bowerrc @@ -0,0 +1,3 @@ +{ + "directory": "wwwroot/lib" +} diff --git a/src/Mvc/samples/MvcSandbox/.gitignore b/src/Mvc/samples/MvcSandbox/.gitignore new file mode 100644 index 0000000000..ea32980d43 --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/.gitignore @@ -0,0 +1,2 @@ +# Ignore everything +* diff --git a/src/Mvc/samples/MvcSandbox/Controllers/HomeController.cs b/src/Mvc/samples/MvcSandbox/Controllers/HomeController.cs new file mode 100644 index 0000000000..2aa4ff6829 --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/Controllers/HomeController.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 Microsoft.AspNetCore.Mvc; + +namespace MvcSandbox.Controllers +{ + public class HomeController : Controller + { + [ModelBinder] + public string Id { get; set; } + + public IActionResult Index() + { + return View(); + } + } +} diff --git a/src/Mvc/samples/MvcSandbox/Models/Index.cs b/src/Mvc/samples/MvcSandbox/Models/Index.cs new file mode 100644 index 0000000000..5f282702bb --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/Models/Index.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Mvc/samples/MvcSandbox/Models/TestModel.cs b/src/Mvc/samples/MvcSandbox/Models/TestModel.cs new file mode 100644 index 0000000000..29e881e9a4 --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/Models/TestModel.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 Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace MvcSandbox +{ + public class TestModel : PageModel + { + public string Name { get; private set; } = "World"; + + public IActionResult OnPost(string name) + { + Name = name; + return Page(); + } + } +} diff --git a/src/Mvc/samples/MvcSandbox/MvcSandbox.csproj b/src/Mvc/samples/MvcSandbox/MvcSandbox.csproj new file mode 100644 index 0000000000..116e4a2f66 --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/MvcSandbox.csproj @@ -0,0 +1,27 @@ + + + + netcoreapp2.1 + $(TargetFrameworks);net461 + + + + true + + + + + + + + + + + + + + + + + + diff --git a/src/Mvc/samples/MvcSandbox/Pages/PagesHome.cshtml b/src/Mvc/samples/MvcSandbox/Pages/PagesHome.cshtml new file mode 100644 index 0000000000..3c565a3b59 --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/Pages/PagesHome.cshtml @@ -0,0 +1,31 @@ +@page +@model TestModel +@{ + + ViewData["Title"] = "Hello from pages"; +} + +@functions { + + [TempData] + public string Message { get; set; } +} + +
+
+

RazorPages says Hello @Model.Name!

+
    +
  • This file should give you a quick view of a Mvc Razor Page in action.
  • +
+

Message from TempData: @Message

+ @{ + Message = $"You visited this page at {DateTime.Now}."; + } +
+ +
+ + +
+ +
diff --git a/src/Mvc/samples/MvcSandbox/Pages/_ViewImports.cshtml b/src/Mvc/samples/MvcSandbox/Pages/_ViewImports.cshtml new file mode 100644 index 0000000000..f4b9eaae4f --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/Pages/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@namespace MvcSandbox.Pages +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/src/Mvc/samples/MvcSandbox/Pages/_ViewStart.cshtml b/src/Mvc/samples/MvcSandbox/Pages/_ViewStart.cshtml new file mode 100644 index 0000000000..26214bbe6c --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/Pages/_ViewStart.cshtml @@ -0,0 +1 @@ +@{ Layout = "_Layout";} \ No newline at end of file diff --git a/src/Mvc/samples/MvcSandbox/Startup.cs b/src/Mvc/samples/MvcSandbox/Startup.cs new file mode 100644 index 0000000000..b963a03a6f --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/Startup.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.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace MvcSandbox +{ + public class Startup + { + // 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); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); + app.UseStaticFiles(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + + public static void Main(string[] args) + { + var host = CreateWebHostBuilder(args) + .Build(); + + host.Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + new WebHostBuilder() + .UseContentRoot(Directory.GetCurrentDirectory()) + .ConfigureLogging(factory => + { + factory + .AddConsole() + .AddDebug(); + }) + .UseIISIntegration() + .UseKestrel() + .UseStartup(); + } +} + diff --git a/src/Mvc/samples/MvcSandbox/Views/Home/Index.cshtml b/src/Mvc/samples/MvcSandbox/Views/Home/Index.cshtml new file mode 100644 index 0000000000..93a5e737b0 --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/Views/Home/Index.cshtml @@ -0,0 +1,12 @@ +@{ + ViewData["Title"] = "Home Page"; +} + +
+
+

Sandbox

+
    +
  • This sandbox should give you a quick view of a basic MVC application.
  • +
+
+
diff --git a/src/Mvc/samples/MvcSandbox/Views/Shared/_Layout.cshtml b/src/Mvc/samples/MvcSandbox/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000000..2308354c26 --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/Views/Shared/_Layout.cshtml @@ -0,0 +1,42 @@ + + + + + + @ViewData["Title"] - MvcSandbox + + + + +
+ @RenderBody() +
+
+

© 2015 - MvcSandbox

+
+
+ + + + + @RenderSection("scripts", required: false) + + diff --git a/src/Mvc/samples/MvcSandbox/Views/_ViewImports.cshtml b/src/Mvc/samples/MvcSandbox/Views/_ViewImports.cshtml new file mode 100644 index 0000000000..96ba4523d1 --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using MvcSandbox +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/src/Mvc/samples/MvcSandbox/Views/_ViewStart.cshtml b/src/Mvc/samples/MvcSandbox/Views/_ViewStart.cshtml new file mode 100644 index 0000000000..a5f10045db --- /dev/null +++ b/src/Mvc/samples/MvcSandbox/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/src/Mvc/src/Directory.Build.props b/src/Mvc/src/Directory.Build.props new file mode 100644 index 0000000000..4b89a431e7 --- /dev/null +++ b/src/Mvc/src/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs new file mode 100644 index 0000000000..a1ced7244b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs @@ -0,0 +1,64 @@ +// Copyright (c) .NET 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.ActionConstraints; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc.Abstractions +{ + public class ActionDescriptor + { + public ActionDescriptor() + { + Id = Guid.NewGuid().ToString(); + Properties = new Dictionary(); + RouteValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + /// Gets an id which uniquely identifies the action. + /// + public string Id { get; } + + /// + /// Gets or sets the collection of route values that must be provided by routing + /// for the action to be selected. + /// + public IDictionary RouteValues { get; set; } + + public AttributeRouteInfo AttributeRouteInfo { get; set; } + + /// + /// The set of constraints for this action. Must all be satisfied for the action to be selected. + /// + public IList ActionConstraints { get; set; } + + /// + /// The set of parameters associated with this action. + /// + public IList Parameters { get; set; } + + /// + /// The set of properties which are model bound. + /// + public IList BoundProperties { get; set; } + + /// + /// The set of filters associated with this action. + /// + public IList FilterDescriptors { get; set; } + + /// + /// A friendly name for this action. + /// + public virtual string DisplayName { get; set; } + + /// + /// Stores arbitrary metadata properties associated with the . + /// + public IDictionary Properties { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorExtensions.cs new file mode 100644 index 0000000000..3cd94db1f1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorExtensions.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET 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.Abstractions +{ + /// + /// Extension methods for . + /// + public static class ActionDescriptorExtensions + { + /// + /// Gets the value of a property from the collection + /// using the provided value of as the key. + /// + /// The type of the property. + /// The action descriptor. + /// The property or the default value of . + public static T GetProperty(this ActionDescriptor actionDescriptor) + { + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + object value; + if (actionDescriptor.Properties.TryGetValue(typeof(T), out value)) + { + return (T)value; + } + else + { + return default(T); + } + } + + /// + /// Sets the value of an property in the collection using + /// the provided value of as the key. + /// + /// The type of the property. + /// The action descriptor. + /// The value of the property. + public static void SetProperty(this ActionDescriptor actionDescriptor, T value) + { + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + actionDescriptor.Properties[typeof(T)] = value; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorProviderContext.cs new file mode 100644 index 0000000000..63ab7dbf0d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorProviderContext.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.Abstractions +{ + public class ActionDescriptorProviderContext + { + public IList Results { get; } = new List(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionInvokerProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionInvokerProviderContext.cs new file mode 100644 index 0000000000..45fd315732 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionInvokerProviderContext.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 Microsoft.AspNetCore.Mvc.Abstractions +{ + public class ActionInvokerProviderContext + { + public ActionInvokerProviderContext(ActionContext actionContext) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + ActionContext = actionContext; + } + + public ActionContext ActionContext { get; } + + public IActionInvoker Result { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionDescriptorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionDescriptorProvider.cs new file mode 100644 index 0000000000..4b52e9aebc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionDescriptorProvider.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. + +namespace Microsoft.AspNetCore.Mvc.Abstractions +{ + public interface IActionDescriptorProvider + { + /// + /// Gets the order value for determining the order of execution of providers. Providers execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Providers are executed in an ordering determined by an ascending sort of the property. + /// A provider with a lower numeric value of will have its + /// called before that of a provider with a higher numeric value of + /// . The method is called in the reverse ordering after + /// all calls to . A provider with a lower numeric value of + /// will have its method called after that of a provider + /// with a higher numeric value of . + /// + /// + /// If two providers have the same numeric value of , then their relative execution order + /// is undefined. + /// + /// + int Order { get; } + + void OnProvidersExecuting(ActionDescriptorProviderContext context); + + void OnProvidersExecuted(ActionDescriptorProviderContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvoker.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvoker.cs new file mode 100644 index 0000000000..ed3650cc23 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvoker.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Abstractions +{ + /// + /// Defines an interface for invoking an MVC action. + /// + /// + /// An is created for each request the MVC handles by querying the set of + /// instances. See for more information. + /// + public interface IActionInvoker + { + /// + /// Invokes an MVC action. + /// + /// A which will complete when action processing has completed. + Task InvokeAsync(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvokerProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvokerProvider.cs new file mode 100644 index 0000000000..c324d7b330 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvokerProvider.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. + +namespace Microsoft.AspNetCore.Mvc.Abstractions +{ + /// + /// Defines an interface for components that can create an for the + /// current request. + /// + /// + /// + /// instances form a pipeline that results in the creation of an + /// . The instances are ordered by + /// an ascending sort of the . + /// + /// + /// To create an , each provider has its method + /// called in sequence and given the same instance of . Then each + /// provider has its method called in the reverse order. The result is + /// the value of . + /// + /// + /// As providers are called in a predefined sequence, each provider has a chance to observe and decorate the + /// result of the providers that have already run. + /// + /// + public interface IActionInvokerProvider + { + /// + /// Gets the order value for determining the order of execution of providers. Providers execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Providers are executed in an ordering determined by an ascending sort of the property. + /// A provider with a lower numeric value of will have its + /// called before that of a provider with a higher numeric value of + /// . The method is called in the reverse ordering after + /// all calls to . A provider with a lower numeric value of + /// will have its method called after that of a provider + /// with a higher numeric value of . + /// + /// + /// If two providers have the same numeric value of , then their relative execution order + /// is undefined. + /// + /// + int Order { get; } + + /// + /// Called to execute the provider. + /// + /// The . + void OnProvidersExecuting(ActionInvokerProviderContext context); + + /// + /// Called to execute the provider, after the methods of all providers, + /// have been called. + /// + /// The . + void OnProvidersExecuted(ActionInvokerProviderContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ParameterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ParameterDescriptor.cs new file mode 100644 index 0000000000..890c3e8526 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ParameterDescriptor.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 System; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Abstractions +{ + public class ParameterDescriptor + { + public string Name { get; set; } + + public Type ParameterType { get; set; } + + public BindingInfo BindingInfo { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintContext.cs new file mode 100644 index 0000000000..68ee228d0e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintContext.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.Collections.Generic; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.ActionConstraints +{ + /// + /// Context for execution. + /// + public class ActionConstraintContext + { + /// + /// The list of . This includes all actions that are valid for the current + /// request, as well as their constraints. + /// + public IReadOnlyList Candidates { get; set; } + + /// + /// The current . + /// + public ActionSelectorCandidate CurrentCandidate { get; set; } + + /// + /// The . + /// + public RouteContext RouteContext { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintItem.cs new file mode 100644 index 0000000000..7022a8f283 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintItem.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; + +namespace Microsoft.AspNetCore.Mvc.ActionConstraints +{ + /// + /// Represents an with or without a corresponding + /// . + /// + public class ActionConstraintItem + { + /// + /// Creates a new . + /// + /// The instance. + public ActionConstraintItem(IActionConstraintMetadata metadata) + { + if (metadata == null) + { + throw new ArgumentNullException(nameof(metadata)); + } + + Metadata = metadata; + } + + /// + /// The associated with . + /// + public IActionConstraint Constraint { get; set; } + + /// + /// The instance. + /// + public IActionConstraintMetadata Metadata { get; } + + /// + /// Gets or sets a value indicating whether or not can be reused across requests. + /// + public bool IsReusable { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintProviderContext.cs new file mode 100644 index 0000000000..d740257203 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintProviderContext.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 System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ActionConstraints +{ + /// + /// Context for an action constraint provider. + /// + public class ActionConstraintProviderContext + { + /// + /// Creates a new . + /// + /// The associated with the request. + /// The for which constraints are being created. + /// The list of objects. + public ActionConstraintProviderContext( + HttpContext context, + ActionDescriptor action, + IList items) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + HttpContext = context; + Action = action; + Results = items; + } + + /// + /// The associated with the request. + /// + public HttpContext HttpContext { get; } + + /// + /// The for which constraints are being created. + /// + public ActionDescriptor Action { get; } + + /// + /// The list of objects. + /// + public IList Results { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionSelectorCandidate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionSelectorCandidate.cs new file mode 100644 index 0000000000..c2251cd371 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionSelectorCandidate.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ActionConstraints +{ + /// + /// A candidate action for action selection. + /// + public struct ActionSelectorCandidate + { + /// + /// Creates a new . + /// + /// The representing a candidate for selection. + /// + /// The list of instances associated with . + /// + public ActionSelectorCandidate(ActionDescriptor action, IReadOnlyList constraints) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + Action = action; + Constraints = constraints; + } + + /// + /// The representing a candidate for selection. + /// + public ActionDescriptor Action { get; } + + /// + /// The list of instances associated with . + /// + public IReadOnlyList Constraints { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraint.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraint.cs new file mode 100644 index 0000000000..bd59ab6c50 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraint.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. + +namespace Microsoft.AspNetCore.Mvc.ActionConstraints +{ + /// + /// Supports conditional logic to determine whether or not an associated action is valid to be selected + /// for the given request. + /// + /// + /// Action constraints have the secondary effect of making an action with a constraint applied a better + /// match than one without. + /// + /// Consider two actions, 'A' and 'B' with the same action and controller name. Action 'A' only allows the + /// HTTP POST method (via a constraint) and action 'B' has no constraints. + /// + /// If an incoming request is a POST, then 'A' is considered the best match because it both matches and + /// has a constraint. If an incoming request uses any other verb, 'A' will not be valid for selection + /// due to it's constraint, so 'B' is the best match. + /// + /// + /// Action constraints are also grouped according to their order value. Any constraints with the same + /// group value are considered to be part of the same application policy, and will be executed in the + /// same stage. + /// + /// Stages run in ascending order based on the value of . Given a set of actions which + /// are candidates for selection, the next stage to run is the lowest value of for any + /// constraint of any candidate which is greater than the order of the last stage. + /// + /// Once the stage order is identified, each action has all of its constraints in that stage executed. + /// If any constraint does not match, then that action is not a candidate for selection. If any actions + /// with constraints in the current state are still candidates, then those are the 'best' actions and this + /// process will repeat with the next stage on the set of 'best' actions. If after processing the + /// subsequent stages of the 'best' actions no candidates remain, this process will repeat on the set of + /// 'other' candidate actions from this stage (those without a constraint). + /// + public interface IActionConstraint : IActionConstraintMetadata + { + /// + /// The constraint order. + /// + /// + /// Constraints are grouped into stages by the value of . See remarks on + /// . + /// + int Order { get; } + + /// + /// Determines whether an action is a valid candidate for selection. + /// + /// The . + /// True if the action is valid for selection, otherwise false. + bool Accept(ActionConstraintContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintFactory.cs new file mode 100644 index 0000000000..9c0f597228 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintFactory.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 System; + +namespace Microsoft.AspNetCore.Mvc.ActionConstraints +{ + /// + /// A factory for . + /// + /// + /// will be invoked during action selection + /// to create constraint instances for an action. + /// + /// Place an attribute implementing this interface on a controller or action to insert an action + /// constraint created by a factory. + /// + public interface IActionConstraintFactory : IActionConstraintMetadata + { + /// + /// Gets a value that indicates if the result of + /// can be reused across requests. + /// + bool IsReusable { get; } + + /// + /// Creates a new . + /// + /// The per-request services. + /// An . + IActionConstraint CreateInstance(IServiceProvider services); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintMetadata.cs new file mode 100644 index 0000000000..cbfd0970c0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintMetadata.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.ActionConstraints +{ + /// + /// A marker interface that identifies a type as metadata for an . + /// + public interface IActionConstraintMetadata + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintProvider.cs new file mode 100644 index 0000000000..8c090462ca --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ActionConstraints +{ + public interface IActionConstraintProvider + { + /// + /// Gets the order value for determining the order of execution of providers. Providers execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Providers are executed in an ordering determined by an ascending sort of the property. + /// A provider with a lower numeric value of will have its + /// called before that of a provider with a higher numeric value of + /// . The method is called in the reverse ordering after + /// all calls to . A provider with a lower numeric value of + /// will have its method called after that of a provider + /// with a higher numeric value of . + /// + /// + /// If two providers have the same numeric value of , then their relative execution order + /// is undefined. + /// + /// + int Order { get; } + + void OnProvidersExecuting(ActionConstraintProviderContext context); + + void OnProvidersExecuted(ActionConstraintProviderContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionContext.cs new file mode 100644 index 0000000000..f7735c1637 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionContext.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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Context object for execution of action which has been selected as part of an HTTP request. + /// + public class ActionContext + { + /// + /// Creates an empty . + /// + /// + /// The default constructor is provided for unit test purposes only. + /// + public ActionContext() + { + ModelState = new ModelStateDictionary(); + } + + /// + /// Creates a new . + /// + /// The to copy. + public ActionContext(ActionContext actionContext) + : this( + actionContext.HttpContext, + actionContext.RouteData, + actionContext.ActionDescriptor, + actionContext.ModelState) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + } + + /// + /// Creates a new . + /// + /// The for the current request. + /// The for the current request. + /// The for the selected action. + public ActionContext( + HttpContext httpContext, + RouteData routeData, + ActionDescriptor actionDescriptor) + : this(httpContext, routeData, actionDescriptor, new ModelStateDictionary()) + { + } + + /// + /// Creates a new . + /// + /// The for the current request. + /// The for the current request. + /// The for the selected action. + /// The . + public ActionContext( + HttpContext httpContext, + RouteData routeData, + ActionDescriptor actionDescriptor, + ModelStateDictionary modelState) + { + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + if (routeData == null) + { + throw new ArgumentNullException(nameof(routeData)); + } + + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + HttpContext = httpContext; + RouteData = routeData; + ActionDescriptor = actionDescriptor; + ModelState = modelState; + } + + /// + /// Gets or sets the for the selected action. + /// + /// + /// The property setter is provided for unit test purposes only. + /// + public ActionDescriptor ActionDescriptor + { + get; set; + } + + /// + /// Gets or sets the for the current request. + /// + /// + /// The property setter is provided for unit test purposes only. + /// + public HttpContext HttpContext + { + get; set; + } + + /// + /// Gets the . + /// + public ModelStateDictionary ModelState + { + get; + } + + /// + /// Gets or sets the for the current request. + /// + /// + /// The property setter is provided for unit test purposes only. + /// + public RouteData RouteData + { + get; set; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescription.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescription.cs new file mode 100644 index 0000000000..827712f1bc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescription.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.Collections.Generic; +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Represents an API exposed by this application. + /// + [DebuggerDisplay("{ActionDescriptor.DisplayName,nq}")] + public class ApiDescription + { + /// + /// Gets or sets for this api. + /// + public ActionDescriptor ActionDescriptor { get; set; } + + /// + /// Gets or sets group name for this api. + /// + public string GroupName { get; set; } + + /// + /// Gets or sets the supported HTTP method for this api, or null if all HTTP methods are supported. + /// + public string HttpMethod { get; set; } + + /// + /// Gets a list of for this api. + /// + public IList ParameterDescriptions { get; } = new List(); + + /// + /// Gets arbitrary metadata properties associated with the . + /// + public IDictionary Properties { get; } = new Dictionary(); + + /// + /// Gets or sets relative url path template (relative to application root) for this api. + /// + public string RelativePath { get; set; } + + /// + /// Gets the list of possible formats for a request. + /// + /// + /// Will be empty if the action does not accept a parameter decorated with the [FromBody] attribute. + /// + public IList SupportedRequestFormats { get; } = new List(); + + /// + /// Gets the list of possible formats for a response. + /// + /// + /// Will be empty if the action returns no response, or if the response type is unclear. Use + /// ProducesAttribute on an action method to specify a response type. + /// + public IList SupportedResponseTypes { get; } = new List(); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescriptionProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescriptionProviderContext.cs new file mode 100644 index 0000000000..aa9d199089 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescriptionProviderContext.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// A context object for providers. + /// + public class ApiDescriptionProviderContext + { + /// + /// Creates a new instance of . + /// + /// The list of actions. + public ApiDescriptionProviderContext(IReadOnlyList actions) + { + if (actions == null) + { + throw new ArgumentNullException(nameof(actions)); + } + + Actions = actions; + + Results = new List(); + } + + /// + /// The list of actions. + /// + public IReadOnlyList Actions { get; } + + /// + /// The list of resulting . + /// + public IList Results { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs new file mode 100644 index 0000000000..1f07d2b7ab --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET 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.Abstractions; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// A metadata description of an input to an API. + /// + public class ApiParameterDescription + { + /// + /// Gets or sets the . + /// + public ModelMetadata ModelMetadata { get; set; } + + /// + /// Gets or sets the name. + /// + public string Name { get; set; } + + /// + /// Gets or sets the . + /// + public ApiParameterRouteInfo RouteInfo { get; set; } + + /// + /// Gets or sets the . + /// + public BindingSource Source { get; set; } + + /// + /// Gets or sets the parameter type. + /// + public Type Type { get; set; } + + /// + /// Gets or sets the parameter descriptor. + /// + public ParameterDescriptor ParameterDescriptor { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterRouteInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterRouteInfo.cs new file mode 100644 index 0000000000..6e5cbfc494 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterRouteInfo.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.Collections.Generic; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// A metadata description of routing information for an . + /// + public class ApiParameterRouteInfo + { + /// + /// Gets or sets the set of objects for the parameter. + /// + /// + /// Route constraints are only applied when a value is bound from a URL's path. See + /// for the data source considered. + /// + public IEnumerable Constraints { get; set; } + + /// + /// Gets or sets the default value for the parameter. + /// + public object DefaultValue { get; set; } + + /// + /// Gets a value indicating whether not a parameter is considered optional by routing. + /// + /// + /// An optional parameter is considered optional by the routing system. This does not imply + /// that the parameter is considered optional by the action. + /// + /// If the parameter uses for the value of + /// then the value may also come from the + /// URL query string or form data. + /// + public bool IsOptional { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiRequestFormat.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiRequestFormat.cs new file mode 100644 index 0000000000..8d93cc65b3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiRequestFormat.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.AspNetCore.Mvc.Formatters; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// A possible format for the body of a request. + /// + public class ApiRequestFormat + { + /// + /// The formatter used to read this request. + /// + public IInputFormatter Formatter { get; set; } + + /// + /// The media type of the request. + /// + public string MediaType { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseFormat.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseFormat.cs new file mode 100644 index 0000000000..bf04db9da7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseFormat.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.AspNetCore.Mvc.Formatters; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Possible format for an . + /// + public class ApiResponseFormat + { + /// + /// Gets or sets the formatter used to output this response. + /// + public IOutputFormatter Formatter { get; set; } + + /// + /// Gets or sets the media type of the response. + /// + public string MediaType { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs new file mode 100644 index 0000000000..6ed9644931 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.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.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Possible type of the response body which is formatted by . + /// + public class ApiResponseType + { + /// + /// Gets or sets the response formats supported by this type. + /// + public IList ApiResponseFormats { get; set; } = new List(); + + /// + /// Gets or sets for the or null. + /// + /// + /// Will be null if is null or void. + /// + public ModelMetadata ModelMetadata { get; set; } + + /// + /// Gets or sets the CLR data type of the response or null. + /// + /// + /// Will be null if the action returns no response, or if the response type is unclear. Use + /// Microsoft.AspNetCore.Mvc.ProducesAttribute or Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute on an action method + /// to specify a response type. + /// + public Type Type { get; set; } + + /// + /// Gets or sets the HTTP response status code. + /// + public int StatusCode { get; set; } + + /// + /// Gets or sets a value indicating whether the response type represents a default response. + /// + /// + /// If an has a default response, then the property should be ignored. This response + /// will be used when a more specific response format does not apply. The common use of a default response is to specify the format + /// for communicating error conditions. + /// + public bool IsDefaultResponse { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/IApiDescriptionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/IApiDescriptionProvider.cs new file mode 100644 index 0000000000..3f3ab9c595 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/IApiDescriptionProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + public interface IApiDescriptionProvider + { + /// + /// Gets the order value for determining the order of execution of providers. Providers execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Providers are executed in an ordering determined by an ascending sort of the property. + /// A provider with a lower numeric value of will have its + /// called before that of a provider with a higher numeric value of + /// . The method is called in the reverse ordering after + /// all calls to . A provider with a lower numeric value of + /// will have its method called after that of a provider + /// with a higher numeric value of . + /// + /// + /// If two providers have the same numeric value of , then their relative execution order + /// is undefined. + /// + /// + int Order { get; } + + /// + /// Creates or modifies s. + /// + /// The . + void OnProvidersExecuting(ApiDescriptionProviderContext context); + + /// + /// Called after implementations with higher values have been called. + /// + /// The . + void OnProvidersExecuted(ApiDescriptionProviderContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Authorization/IAllowAnonymousFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Authorization/IAllowAnonymousFilter.cs new file mode 100644 index 0000000000..a015f05967 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Authorization/IAllowAnonymousFilter.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.Filters; + +namespace Microsoft.AspNetCore.Mvc.Authorization +{ + /// + /// A filter that allows anonymous requests, disabling some s. + /// + public interface IAllowAnonymousFilter : IFilterMetadata + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutedContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutedContext.cs new file mode 100644 index 0000000000..68e0343f04 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutedContext.cs @@ -0,0 +1,96 @@ +// Copyright (c) .NET 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.Runtime.ExceptionServices; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for action filters, specifically calls. + /// + public class ActionExecutedContext : FilterContext + { + private Exception _exception; + private ExceptionDispatchInfo _exceptionDispatchInfo; + + /// + /// Instantiates a new instance. + /// + /// The . + /// All applicable implementations. + /// The controller instance containing the action. + public ActionExecutedContext( + ActionContext actionContext, + IList filters, + object controller) + : base(actionContext, filters) + { + Controller = controller; + } + + /// + /// Gets or sets an indication that an action filter short-circuited the action and the action filter pipeline. + /// + public virtual bool Canceled { get; set; } + + /// + /// Gets the controller instance containing the action. + /// + public virtual object Controller { get; } + + /// + /// Gets or sets the caught while executing the action or action filters, if + /// any. + /// + public virtual Exception Exception + { + get + { + if (_exception == null && _exceptionDispatchInfo != null) + { + return _exceptionDispatchInfo.SourceException; + } + else + { + return _exception; + } + } + + set + { + _exceptionDispatchInfo = null; + _exception = value; + } + } + + /// + /// Gets or sets the for the + /// , if an was caught and this information captured. + /// + public virtual ExceptionDispatchInfo ExceptionDispatchInfo + { + get + { + return _exceptionDispatchInfo; + } + + set + { + _exception = null; + _exceptionDispatchInfo = value; + } + } + + /// + /// Gets or sets an indication that the has been handled. + /// + public virtual bool ExceptionHandled { get; set; } + + /// + /// Gets or sets the . + /// + public virtual IActionResult Result { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutingContext.cs new file mode 100644 index 0000000000..65ade1e1c5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutingContext.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for action filters, specifically and + /// calls. + /// + public class ActionExecutingContext : FilterContext + { + /// + /// Instantiates a new instance. + /// + /// The . + /// All applicable implementations. + /// + /// The arguments to pass when invoking the action. Keys are parameter names. + /// + /// The controller instance containing the action. + public ActionExecutingContext( + ActionContext actionContext, + IList filters, + IDictionary actionArguments, + object controller) + : base(actionContext, filters) + { + if (actionArguments == null) + { + throw new ArgumentNullException(nameof(actionArguments)); + } + + ActionArguments = actionArguments; + Controller = controller; + } + + /// + /// Gets or sets the to execute. Setting to a non-null + /// value inside an action filter will short-circuit the action and any remaining action filters. + /// + public virtual IActionResult Result { get; set; } + + /// + /// Gets the arguments to pass when invoking the action. Keys are parameter names. + /// + public virtual IDictionary ActionArguments { get; } + + /// + /// Gets the controller instance containing the action. + /// + public virtual object Controller { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutionDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutionDelegate.cs new file mode 100644 index 0000000000..0ee53b4493 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutionDelegate.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 System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A delegate that asynchronously returns an indicating the action or the next + /// action filter has executed. + /// + /// + /// A that on completion returns an . + /// + public delegate Task ActionExecutionDelegate(); +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/AuthorizationFilterContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/AuthorizationFilterContext.cs new file mode 100644 index 0000000000..a344d55432 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/AuthorizationFilterContext.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for authorization filters i.e. and + /// implementations. + /// + public class AuthorizationFilterContext : FilterContext + { + /// + /// Instantiates a new instance. + /// + /// The . + /// All applicable implementations. + public AuthorizationFilterContext( + ActionContext actionContext, + IList filters) + : base(actionContext, filters) + { + } + + /// + /// Gets or sets the result of the request. Setting to a non-null value inside + /// an authorization filter will short-circuit the remainder of the filter pipeline. + /// + public virtual IActionResult Result { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ExceptionContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ExceptionContext.cs new file mode 100644 index 0000000000..c0920f057b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ExceptionContext.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET 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.Runtime.ExceptionServices; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for exception filters i.e. and + /// implementations. + /// + public class ExceptionContext : FilterContext + { + private Exception _exception; + private ExceptionDispatchInfo _exceptionDispatchInfo; + + /// + /// Instantiates a new instance. + /// + /// The . + /// All applicable implementations. + public ExceptionContext(ActionContext actionContext, IList filters) + : base(actionContext, filters) + { + } + + /// + /// Gets or sets the caught while executing the action. + /// + public virtual Exception Exception + { + get + { + if (_exception == null && _exceptionDispatchInfo != null) + { + return _exceptionDispatchInfo.SourceException; + } + else + { + return _exception; + } + } + + set + { + _exceptionDispatchInfo = null; + _exception = value; + } + } + + /// + /// Gets or sets the for the + /// , if this information was captured. + /// + public virtual ExceptionDispatchInfo ExceptionDispatchInfo + { + get + { + return _exceptionDispatchInfo; + } + + set + { + _exception = null; + _exceptionDispatchInfo = value; + } + } + + /// + /// Gets or sets an indication that the has been handled. + /// + public virtual bool ExceptionHandled { get; set; } + + /// + /// Gets or sets the . + /// + public virtual IActionResult Result { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterContext.cs new file mode 100644 index 0000000000..961e283399 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterContext.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// An abstract context for filters. + /// + public abstract class FilterContext : ActionContext + { + /// + /// Instantiates a new instance. + /// + /// The . + /// All applicable implementations. + public FilterContext( + ActionContext actionContext, + IList filters) + : base(actionContext) + { + if (filters == null) + { + throw new ArgumentNullException(nameof(filters)); + } + + Filters = filters; + } + + /// + /// Gets all applicable implementations. + /// + public virtual IList Filters { get; } + + /// + /// Returns a value indicating whether the provided is the most effective + /// policy (most specific) applied to the action associated with the . + /// + /// The type of the filter policy. + /// The filter policy instance. + /// + /// true if the provided is the most effective policy, otherwise false. + /// + /// + /// + /// The method is used to implement a common convention + /// for filters that define an overriding behavior. When multiple filters may apply to the same + /// cross-cutting concern, define a common interface for the filters () and + /// implement the filters such that all of the implementations call this method to determine if they should + /// take action. + /// + /// + /// For instance, a global filter might be overridden by placing a filter attribute on an action method. + /// The policy applied directly to the action method could be considered more specific. + /// + /// + /// This mechanism for overriding relies on the rules of order and scope that the filter system + /// provides to control ordering of filters. It is up to the implementor of filters to implement this + /// protocol cooperatively. The filter system has no innate notion of overrides, this is a recommended + /// convention. + /// + /// + public bool IsEffectivePolicy(TMetadata policy) where TMetadata : IFilterMetadata + { + if (policy == null) + { + throw new ArgumentNullException(nameof(policy)); + } + + var effective = FindEffectivePolicy(); + return ReferenceEquals(policy, effective); + } + + /// + /// Returns the most effective (most specific) policy of type applied to + /// the action associated with the . + /// + /// The type of the filter policy. + /// The implementation of applied to the action associated with + /// the + /// + public TMetadata FindEffectivePolicy() where TMetadata : IFilterMetadata + { + // The most specific policy is the one closest to the action (nearest the end of the list). + for (var i = Filters.Count - 1; i >= 0; i--) + { + var filter = Filters[i]; + if (filter is TMetadata match) + { + return match; + } + } + + return default(TMetadata); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterDescriptor.cs new file mode 100644 index 0000000000..a84ea30133 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterDescriptor.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.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// Descriptor for an . + /// + /// + /// describes an with an order and scope. + /// + /// Order and scope control the execution order of filters. Filters with a higher value of Order execute + /// later in the pipeline. + /// + /// When filters have the same Order, the Scope value is used to determine the order of execution. Filters + /// with a higher value of Scope execute later in the pipeline. See Microsoft.AspNetCore.Mvc.FilterScope + /// for commonly used scopes. + /// + /// For implementations, the filter runs only after an exception has occurred, + /// and so the observed order of execution will be opposite that of other filters. + /// + [DebuggerDisplay("Filter = {Filter.ToString(),nq}, Order = {Order}")] + public class FilterDescriptor + { + /// + /// Creates a new . + /// + /// The . + /// The filter scope. + /// + /// If the implements , then the value of + /// will be taken from . Otherwise the value + /// of will default to 0. + /// + public FilterDescriptor(IFilterMetadata filter, int filterScope) + { + if (filter == null) + { + throw new ArgumentNullException(nameof(filter)); + } + + Filter = filter; + Scope = filterScope; + + + if (Filter is IOrderedFilter orderedFilter) + { + Order = orderedFilter.Order; + } + } + + /// + /// The instance. + /// + public IFilterMetadata Filter { get; } + + /// + /// The filter order. + /// + public int Order { get; set; } + + /// + /// The filter scope. + /// + public int Scope { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterItem.cs new file mode 100644 index 0000000000..516acd7d24 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterItem.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; +using System.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// Used to associate executable filters with instances + /// as part of . An should + /// inspect and set and + /// as appropriate. + /// + [DebuggerDisplay("FilterItem: {Filter}")] + public class FilterItem + { + /// + /// Creates a new . + /// + /// The . + public FilterItem(FilterDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + Descriptor = descriptor; + } + + /// + /// Creates a new . + /// + /// The . + /// + public FilterItem(FilterDescriptor descriptor, IFilterMetadata filter) + : this(descriptor) + { + if (filter == null) + { + throw new ArgumentNullException(nameof(filter)); + } + + Filter = filter; + } + + /// + /// Gets the containing the filter metadata. + /// + public FilterDescriptor Descriptor { get; } + + /// + /// Gets or sets the executable associated with . + /// + public IFilterMetadata Filter { get; set; } + + /// + /// Gets or sets a value indicating whether or not can be reused across requests. + /// + public bool IsReusable { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterProviderContext.cs new file mode 100644 index 0000000000..057654eaa4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterProviderContext.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 System.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for filter providers i.e. implementations. + /// + public class FilterProviderContext + { + /// + /// Instantiates a new instance. + /// + /// The . + /// + /// The s, initially created from s or a cache entry. + /// + public FilterProviderContext(ActionContext actionContext, IList items) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + ActionContext = actionContext; + Results = items; + } + + /// + /// Gets or sets the . + /// + public ActionContext ActionContext { get; set; } + + /// + /// Gets or sets the s, initially created from s or a + /// cache entry. s should set on existing items or + /// add new s to make executable filters available. + /// + public IList Results { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IActionFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IActionFilter.cs new file mode 100644 index 0000000000..cb37ded4c7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IActionFilter.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. + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that surrounds execution of the action. + /// + public interface IActionFilter : IFilterMetadata + { + /// + /// Called before the action executes, after model binding is complete. + /// + /// The . + void OnActionExecuting(ActionExecutingContext context); + + /// + /// Called after the action executes, before the action result. + /// + /// The . + void OnActionExecuted(ActionExecutedContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAlwaysRunResultFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAlwaysRunResultFilter.cs new file mode 100644 index 0000000000..e119fd8d93 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAlwaysRunResultFilter.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. + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that surrounds execution of all action results. + /// + /// + /// + /// The interface declares an implementation + /// that should run for all action results. . + /// + /// + /// and instances are not executed in cases where + /// an authorization filter or resource filter short-circuits the request to prevent execution of the action. + /// and implementations + /// are also not executed in cases where an exception filter handles an exception by producing an action result. + /// + /// + public interface IAlwaysRunResultFilter : IResultFilter + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncActionFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncActionFilter.cs new file mode 100644 index 0000000000..3342e2b1ff --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncActionFilter.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that asynchronously surrounds execution of the action, after model binding is complete. + /// + public interface IAsyncActionFilter : IFilterMetadata + { + /// + /// Called asynchronously before the action, after model binding is complete. + /// + /// The . + /// + /// The . Invoked to execute the next action filter or the action itself. + /// + /// A that on completion indicates the filter has executed. + Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAlwaysRunResultFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAlwaysRunResultFilter.cs new file mode 100644 index 0000000000..f3c0c94dd8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAlwaysRunResultFilter.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. + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that asynchronously surrounds execution of all action results. + /// + /// + /// + /// The interface declares an implementation + /// that should run for all action results. . + /// + /// + /// and instances are not executed in cases where + /// an authorization filter or resource filter short-circuits the request to prevent execution of the action. + /// and implementations + /// are also not executed in cases where an exception filter handles an exception by producing an action result. + /// + /// + public interface IAsyncAlwaysRunResultFilter : IAsyncResultFilter + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAuthorizationFilter.cs new file mode 100644 index 0000000000..dd1a797e13 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAuthorizationFilter.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that asynchronously confirms request authorization. + /// + public interface IAsyncAuthorizationFilter : IFilterMetadata + { + /// + /// Called early in the filter pipeline to confirm request is authorized. + /// + /// The . + /// + /// A that on completion indicates the filter has executed. + /// + Task OnAuthorizationAsync(AuthorizationFilterContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncExceptionFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncExceptionFilter.cs new file mode 100644 index 0000000000..ce3343b463 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncExceptionFilter.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that runs asynchronously after an action has thrown an . + /// + public interface IAsyncExceptionFilter : IFilterMetadata + { + /// + /// Called after an action has thrown an . + /// + /// The . + /// A that on completion indicates the filter has executed. + Task OnExceptionAsync(ExceptionContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResourceFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResourceFilter.cs new file mode 100644 index 0000000000..6b4eb7ba9e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResourceFilter.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that asynchronously surrounds execution of model binding, the action (and filters) and the action + /// result (and filters). + /// + public interface IAsyncResourceFilter : IFilterMetadata + { + /// + /// Called asynchronously before the rest of the pipeline. + /// + /// The . + /// + /// The . Invoked to execute the next resource filter or the remainder + /// of the pipeline. + /// + /// + /// A which will complete when the remainder of the pipeline completes. + /// + Task OnResourceExecutionAsync( + ResourceExecutingContext context, + ResourceExecutionDelegate next); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResultFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResultFilter.cs new file mode 100644 index 0000000000..d6583cbc60 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResultFilter.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that asynchronously surrounds execution of action results successfully returned from an action. + /// + /// + /// + /// and implementations are executed around the action + /// result only when the action method (or action filters) complete successfully. + /// + /// + /// and instances are not executed in cases where + /// an authorization filter or resource filter short-circuits the request to prevent execution of the action. + /// . and implementations + /// are also not executed in cases where an exception filter handles an exception by producing an action result. + /// + /// + /// To create a result filter that surrounds the execution of all action results, implement + /// either the or the interface. + /// + /// + public interface IAsyncResultFilter : IFilterMetadata + { + /// + /// Called asynchronously before the action result. + /// + /// The . + /// + /// The . Invoked to execute the next result filter or the result itself. + /// + /// A that on completion indicates the filter has executed. + Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAuthorizationFilter.cs new file mode 100644 index 0000000000..3dbc4159a0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAuthorizationFilter.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.Filters +{ + /// + /// A filter that confirms request authorization. + /// + public interface IAuthorizationFilter : IFilterMetadata + { + /// + /// Called early in the filter pipeline to confirm request is authorized. + /// + /// The . + void OnAuthorization(AuthorizationFilterContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IExceptionFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IExceptionFilter.cs new file mode 100644 index 0000000000..5908c2aac9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IExceptionFilter.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.Filters +{ + /// + /// A filter that runs after an action has thrown an . + /// + public interface IExceptionFilter : IFilterMetadata + { + /// + /// Called after an action has thrown an . + /// + /// The . + void OnException(ExceptionContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterContainer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterContainer.cs new file mode 100644 index 0000000000..26199e6dcf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterContainer.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. + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that requires a reference back to the that created it. + /// + public interface IFilterContainer + { + /// + /// The that created this filter instance. + /// + IFilterMetadata FilterDefinition { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterFactory.cs new file mode 100644 index 0000000000..e0e844a790 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterFactory.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.Filters +{ + /// + /// An interface for filter metadata which can create an instance of an executable filter. + /// + public interface IFilterFactory : IFilterMetadata + { + /// + /// Gets a value that indicates if the result of + /// can be reused across requests. + /// + bool IsReusable { get; } + + /// + /// Creates an instance of the executable filter. + /// + /// The request . + /// An instance of the executable filter. + IFilterMetadata CreateInstance(IServiceProvider serviceProvider); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterMetadata.cs new file mode 100644 index 0000000000..458bfdc35c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterMetadata.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.Filters +{ + /// + /// Marker interface for filters handled in the MVC request pipeline. + /// + public interface IFilterMetadata + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterProvider.cs new file mode 100644 index 0000000000..46f121b4e8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterProvider.cs @@ -0,0 +1,45 @@ +// Copyright (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.Filters +{ + /// + /// A provider. Implementations should update + /// to make executable filters available. + /// + public interface IFilterProvider + { + /// + /// Gets the order value for determining the order of execution of providers. Providers execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Providers are executed in an ordering determined by an ascending sort of the property. + /// A provider with a lower numeric value of will have its + /// called before that of a provider with a higher numeric value of + /// . The method is called in the reverse ordering after + /// all calls to . A provider with a lower numeric value of + /// will have its method called after that of a provider + /// with a higher numeric value of . + /// + /// + /// If two providers have the same numeric value of , then their relative execution order + /// is undefined. + /// + /// + int Order { get; } + + /// + /// Called in increasing . + /// + /// The . + void OnProvidersExecuting(FilterProviderContext context); + + /// + /// Called in decreasing , after all s have executed once. + /// + /// The . + void OnProvidersExecuted(FilterProviderContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IOrderedFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IOrderedFilter.cs new file mode 100644 index 0000000000..36876091ce --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IOrderedFilter.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. + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that specifies the relative order it should run. + /// + public interface IOrderedFilter : IFilterMetadata + { + /// + /// Gets the order value for determining the order of execution of filters. Filters execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Filters are executed in an ordering determined by an ascending sort of the property. + /// + /// + /// Asynchronous filters, such as , surround the execution of subsequent + /// filters of the same filter kind. An asynchronous filter with a lower numeric + /// value will have its filter method, such as , + /// executed before that of a filter with a higher value of . + /// + /// + /// Synchronous filters, such as , have a before-method, such as + /// , and an after-method, such as + /// . A synchronous filter with a lower numeric + /// value will have its before-method executed before that of a filter with a higher value of + /// . During the after-stage of the filter, a synchronous filter with a lower + /// numeric value will have its after-method executed after that of a filter with a higher + /// value of . + /// + /// + /// If two filters have the same numeric value of , then their relative execution order + /// is determined by the filter scope. + /// + /// + int Order { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResourceFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResourceFilter.cs new file mode 100644 index 0000000000..410a03c939 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResourceFilter.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. + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that surrounds execution of model binding, the action (and filters) and the action result + /// (and filters). + /// + public interface IResourceFilter : IFilterMetadata + { + /// + /// Executes the resource filter. Called before execution of the remainder of the pipeline. + /// + /// The . + void OnResourceExecuting(ResourceExecutingContext context); + + /// + /// Executes the resource filter. Called after execution of the remainder of the pipeline. + /// + /// The . + void OnResourceExecuted(ResourceExecutedContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResultFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResultFilter.cs new file mode 100644 index 0000000000..bb0ba46bbb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResultFilter.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.Filters +{ + /// + /// A filter that surrounds execution of action results successfully returned from an action. + /// + /// + /// + /// and implementations are executed around the action + /// result only when the action method (or action filters) complete successfully. + /// + /// + /// and instances are not executed in cases where + /// an authorization filter or resource filter short-circuits the request to prevent execution of the action. + /// . and implementations + /// are also not executed in cases where an exception filter handles an exception by producing an action result. + /// + /// + /// To create a result filter that surrounds the execution of all action results, implement + /// either the or the interface. + /// + /// + public interface IResultFilter : IFilterMetadata + { + /// + /// Called before the action result executes. + /// + /// The . + void OnResultExecuting(ResultExecutingContext context); + + /// + /// Called after the action result executes. + /// + /// The . + void OnResultExecuted(ResultExecutedContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutedContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutedContext.cs new file mode 100644 index 0000000000..6a54d2c410 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutedContext.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; +using System.Collections.Generic; +using System.Runtime.ExceptionServices; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for resource filters, specifically calls. + /// + public class ResourceExecutedContext : FilterContext + { + private Exception _exception; + private ExceptionDispatchInfo _exceptionDispatchInfo; + + /// + /// Creates a new . + /// + /// The . + /// The list of instances. + public ResourceExecutedContext(ActionContext actionContext, IList filters) + : base(actionContext, filters) + { + } + + /// + /// Gets or sets a value which indicates whether or not execution was canceled by a resource filter. + /// If true, then a resource filter short-circuited execution by setting + /// . + /// + public virtual bool Canceled { get; set; } + + /// + /// Gets or set the current . + /// + /// + /// + /// Setting or to null will treat + /// the exception as handled, and it will not be rethrown by the runtime. + /// + /// + /// Setting to true will also mark the exception as handled. + /// + /// + public virtual Exception Exception + { + get + { + if (_exception == null && _exceptionDispatchInfo != null) + { + return _exceptionDispatchInfo.SourceException; + } + else + { + return _exception; + } + } + + set + { + _exceptionDispatchInfo = null; + _exception = value; + } + } + + /// + /// Gets or set the current . + /// + /// + /// + /// Setting or to null will treat + /// the exception as handled, and it will not be rethrown by the runtime. + /// + /// + /// Setting to true will also mark the exception as handled. + /// + /// + public virtual ExceptionDispatchInfo ExceptionDispatchInfo + { + get + { + return _exceptionDispatchInfo; + } + + set + { + _exception = null; + _exceptionDispatchInfo = value; + } + } + + /// + /// + /// Gets or sets a value indicating whether or not the current has been handled. + /// + /// + /// If false the will be rethrown by the runtime after resource filters + /// have executed. + /// + /// + public virtual bool ExceptionHandled { get; set; } + + /// + /// Gets or sets the result. + /// + /// + /// + /// The may be provided by execution of the action itself or by another + /// filter. + /// + /// + /// The has already been written to the response before being made available + /// to resource filters. + /// + /// + public virtual IActionResult Result { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutingContext.cs new file mode 100644 index 0000000000..46378b3760 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutingContext.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; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for resource filters, specifically and + /// calls. + /// + public class ResourceExecutingContext : FilterContext + { + /// + /// Creates a new . + /// + /// The . + /// The list of instances. + /// The list of instances. + public ResourceExecutingContext( + ActionContext actionContext, + IList filters, + IList valueProviderFactories) + : base(actionContext, filters) + { + if (valueProviderFactories == null) + { + throw new ArgumentNullException(nameof(valueProviderFactories)); + } + + ValueProviderFactories = valueProviderFactories; + } + + /// + /// Gets or sets the result of the action to be executed. + /// + /// + /// Setting to a non-null value inside a resource filter will + /// short-circuit execution of additional resource filters and the action itself. + /// + public virtual IActionResult Result { get; set; } + + /// + /// Gets the list of instances used by model binding. + /// + public IList ValueProviderFactories { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutionDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutionDelegate.cs new file mode 100644 index 0000000000..ef3531d208 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutionDelegate.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A delegate that asynchronously returns a indicating model binding, the + /// action, the action's result, result filters, and exception filters have executed. + /// + /// A that on completion returns a . + public delegate Task ResourceExecutionDelegate(); +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutedContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutedContext.cs new file mode 100644 index 0000000000..ae4947d8bf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutedContext.cs @@ -0,0 +1,107 @@ +// Copyright (c) .NET 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.Runtime.ExceptionServices; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for result filters, specifically calls. + /// + public class ResultExecutedContext : FilterContext + { + private Exception _exception; + private ExceptionDispatchInfo _exceptionDispatchInfo; + + /// + /// Instantiates a new instance. + /// + /// The . + /// All applicable implementations. + /// + /// The copied from . + /// + /// The controller instance containing the action. + public ResultExecutedContext( + ActionContext actionContext, + IList filters, + IActionResult result, + object controller) + : base(actionContext, filters) + { + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + Result = result; + Controller = controller; + } + + /// + /// Gets or sets an indication that a result filter set to + /// true and short-circuited the filter pipeline. + /// + public virtual bool Canceled { get; set; } + + /// + /// Gets the controller instance containing the action. + /// + public virtual object Controller { get; } + + /// + /// Gets or sets the caught while executing the result or result filters, if + /// any. + /// + public virtual Exception Exception + { + get + { + if (_exception == null && _exceptionDispatchInfo != null) + { + return _exceptionDispatchInfo.SourceException; + } + else + { + return _exception; + } + } + + set + { + _exceptionDispatchInfo = null; + _exception = value; + } + } + + /// + /// Gets or sets the for the + /// , if an was caught and this information captured. + /// + public virtual ExceptionDispatchInfo ExceptionDispatchInfo + { + get + { + return _exceptionDispatchInfo; + } + + set + { + _exception = null; + _exceptionDispatchInfo = value; + } + } + + /// + /// Gets or sets an indication that the has been handled. + /// + public virtual bool ExceptionHandled { get; set; } + + /// + /// Gets the copied from . + /// + public virtual IActionResult Result { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutingContext.cs new file mode 100644 index 0000000000..cd4e8775ac --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutingContext.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for result filters, specifically and + /// calls. + /// + public class ResultExecutingContext : FilterContext + { + /// + /// Instantiates a new instance. + /// + /// The . + /// All applicable implementations. + /// The of the action and action filters. + /// The controller instance containing the action. + public ResultExecutingContext( + ActionContext actionContext, + IList filters, + IActionResult result, + object controller) + : base(actionContext, filters) + { + Result = result; + Controller = controller; + } + + /// + /// Gets the controller instance containing the action. + /// + public virtual object Controller { get; } + + /// + /// Gets or sets the to execute. Setting to a non-null + /// value inside a result filter will short-circuit the result and any remaining result filters. + /// + public virtual IActionResult Result { get; set; } + + /// + /// Gets or sets an indication the result filter pipeline should be short-circuited. + /// + public virtual bool Cancel { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutionDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutionDelegate.cs new file mode 100644 index 0000000000..d0ecea6914 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutionDelegate.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A delegate that asynchronously returns an indicating the action result or + /// the next result filter has executed. + /// + /// A that on completion returns an . + public delegate Task ResultExecutionDelegate(); +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/FormatterCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/FormatterCollection.cs new file mode 100644 index 0000000000..401febb5cf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/FormatterCollection.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.Collections.Generic; +using System.Collections.ObjectModel; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Represents a collection of formatters. + /// + /// The type of formatters in the collection. + public class FormatterCollection : Collection + { + /// + /// Initializes a new instance of the class that is empty. + /// + public FormatterCollection() + { + } + + /// + /// Initializes a new instance of the class + /// as a wrapper for the specified list. + /// + /// The list that is wrapped by the new collection. + public FormatterCollection(IList list) + : base(list) + { + } + + /// + /// Removes all formatters of the specified type. + /// + /// The type to remove. + public void RemoveType() where T : TFormatter + { + RemoveType(typeof(T)); + } + + /// + /// Removes all formatters of the specified type. + /// + /// The type to remove. + public void RemoveType(Type formatterType) + { + for (var i = Count - 1; i >= 0; i--) + { + var formatter = this[i]; + if (formatter.GetType() == formatterType) + { + RemoveAt(i); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatter.cs new file mode 100644 index 0000000000..c56633d16a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatter.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Reads an object from the request body. + /// + public interface IInputFormatter + { + /// + /// Determines whether this can deserialize an object of the + /// 's . + /// + /// The . + /// + /// true if this can deserialize an object of the + /// 's . false otherwise. + /// + bool CanRead(InputFormatterContext context); + + /// + /// Reads an object from the request body. + /// + /// The . + /// A that on completion deserializes the request body. + Task ReadAsync(InputFormatterContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatterExceptionPolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatterExceptionPolicy.cs new file mode 100644 index 0000000000..c2032c0e05 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatterExceptionPolicy.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. + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// A policy which s can implement to indicate if they want the body model binder + /// to handle all exceptions. By default, all default s implement this interface and + /// have a default value of . + /// + public interface IInputFormatterExceptionPolicy + { + /// + /// Gets the flag to indicate if the body model binder should handle all exceptions. If an exception is handled, + /// the body model binder converts the exception into model state errors, else the exception is allowed to propagate. + /// + InputFormatterExceptionPolicy ExceptionPolicy { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IOutputFormatter.cs new file mode 100644 index 0000000000..a266cf2df7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IOutputFormatter.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 System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Writes an object to the output stream. + /// + public interface IOutputFormatter + { + /// + /// Determines whether this can serialize + /// an object of the specified type. + /// + /// The formatter context associated with the call. + /// Returns true if the formatter can write the response; false otherwise. + bool CanWriteResult(OutputFormatterCanWriteContext context); + + /// + /// Writes the object represented by 's Object property. + /// + /// The formatter context associated with the call. + /// A Task that serializes the value to the 's response message. + Task WriteAsync(OutputFormatterWriteContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterContext.cs new file mode 100644 index 0000000000..001a24dd99 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterContext.cs @@ -0,0 +1,143 @@ +// Copyright (c) .NET 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.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// A context object used by an input formatter for deserializing the request body into an object. + /// + public class InputFormatterContext + { + /// + /// Creates a new instance of . + /// + /// + /// The for the current operation. + /// + /// The name of the model. + /// + /// The for recording errors. + /// + /// + /// The of the model to deserialize. + /// + /// + /// A delegate which can create a for the request body. + /// + public InputFormatterContext( + HttpContext httpContext, + string modelName, + ModelStateDictionary modelState, + ModelMetadata metadata, + Func readerFactory) + : this(httpContext, modelName, modelState, metadata, readerFactory, treatEmptyInputAsDefaultValue: false) + { + } + + /// + /// Creates a new instance of . + /// + /// + /// The for the current operation. + /// + /// The name of the model. + /// + /// The for recording errors. + /// + /// + /// The of the model to deserialize. + /// + /// + /// A delegate which can create a for the request body. + /// + /// + /// A value for the property. + /// + public InputFormatterContext( + HttpContext httpContext, + string modelName, + ModelStateDictionary modelState, + ModelMetadata metadata, + Func readerFactory, + bool treatEmptyInputAsDefaultValue) + { + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + if (modelName == null) + { + throw new ArgumentNullException(nameof(modelName)); + } + + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + if (metadata == null) + { + throw new ArgumentNullException(nameof(metadata)); + } + + if (readerFactory == null) + { + throw new ArgumentNullException(nameof(readerFactory)); + } + + HttpContext = httpContext; + ModelName = modelName; + ModelState = modelState; + Metadata = metadata; + ReaderFactory = readerFactory; + TreatEmptyInputAsDefaultValue = treatEmptyInputAsDefaultValue; + ModelType = metadata.ModelType; + } + + /// + /// Gets a flag to indicate whether the input formatter should allow no value to be provided. + /// If , the input formatter should handle empty input by returning + /// . If , the input + /// formatter should handle empty input by returning the default value for the type + /// . + /// + public bool TreatEmptyInputAsDefaultValue { get; } + + /// + /// Gets the associated with the current operation. + /// + public HttpContext HttpContext { get; } + + /// + /// Gets the name of the model. Used as the key or key prefix for errors added to . + /// + public string ModelName { get; } + + /// + /// Gets the associated with the current operation. + /// + public ModelStateDictionary ModelState { get; } + + /// + /// Gets the requested of the request body deserialization. + /// + public ModelMetadata Metadata { get; } + + /// + /// Gets the requested of the request body deserialization. + /// + public Type ModelType { get; } + + /// + /// Gets a delegate which can create a for the request body. + /// + public Func ReaderFactory { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterException.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterException.cs new file mode 100644 index 0000000000..135d71d701 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterException.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.Formatters +{ + /// + /// Exception thrown by when the input is not in an expected format. + /// + public class InputFormatterException : Exception + { + public InputFormatterException() + { + } + + public InputFormatterException(string message) + : base(message) + { + } + + public InputFormatterException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterExceptionPolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterExceptionPolicy.cs new file mode 100644 index 0000000000..be64d4d3ac --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterExceptionPolicy.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; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Defines the set of policies that determine how the model binding system interprets exceptions + /// thrown by an . Applications should set + /// MvcOptions.InputFormatterExceptionPolicy to configure this setting. + /// + /// + /// + /// An could throw an exception for several reasons, including: + /// + /// malformed input + /// client disconnect or other I/O problem + /// + /// application configuration problems such as + /// + /// + /// + /// + /// The policy associated with treats + /// all such categories of problems as model state errors, and usually will be reported to the client as + /// an HTTP 400. This was the only policy supported by model binding in ASP.NET Core MVC 1.0, 1.1, and 2.0 + /// and is still the default for historical reasons. + /// + /// + /// The policy associated with + /// treats only and its subclasses as model state errors. This means that + /// exceptions that are not related to the content of the HTTP request (such as a disconnect) will be rethrown, + /// which by default would cause an HTTP 500 response, unless there is exception-handling middleware enabled. + /// + /// + public enum InputFormatterExceptionPolicy + { + /// + /// This value indicates that all exceptions thrown by an will be treated + /// as model state errors. + /// + AllExceptions = 0, + + /// + /// This value indicates that only and subclasses will be treated + /// as model state errors. All other exceptions types will be rethrown and can be handled by a higher + /// level exception handler, such as exception-handling middleware. + /// + MalformedInputExceptions = 1, + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterResult.cs new file mode 100644 index 0000000000..2304cd5671 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterResult.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Result of a operation. + /// + public class InputFormatterResult + { + private static readonly InputFormatterResult _failure = new InputFormatterResult(hasError: true); + private static readonly InputFormatterResult _noValue = new InputFormatterResult(hasError: false); + private static readonly Task _failureAsync = Task.FromResult(_failure); + private static readonly Task _noValueAsync = Task.FromResult(_noValue); + + private InputFormatterResult(bool hasError) + { + HasError = hasError; + } + + private InputFormatterResult(object model) + { + Model = model; + IsModelSet = true; + } + + /// + /// Gets an indication whether the operation had an error. + /// + public bool HasError { get; } + + /// + /// Gets an indication whether a value for the property was supplied. + /// + public bool IsModelSet { get; } + + /// + /// Gets the deserialized . + /// + /// + /// null if is true. + /// + public object Model { get; } + + /// + /// Returns an indicating the + /// operation failed. + /// + /// + /// An indicating the + /// operation failed i.e. with true. + /// + public static InputFormatterResult Failure() + { + return _failure; + } + + /// + /// Returns a that on completion provides an indicating + /// the operation failed. + /// + /// + /// A that on completion provides an indicating the + /// operation failed i.e. with true. + /// + public static Task FailureAsync() + { + return _failureAsync; + } + + /// + /// Returns an indicating the + /// operation was successful. + /// + /// The deserialized . + /// + /// An indicating the + /// operation succeeded i.e. with false. + /// + public static InputFormatterResult Success(object model) + { + return new InputFormatterResult(model); + } + + /// + /// Returns a that on completion provides an indicating + /// the operation was successful. + /// + /// The deserialized . + /// + /// A that on completion provides an indicating the + /// operation succeeded i.e. with false. + /// + public static Task SuccessAsync(object model) + { + return Task.FromResult(Success(model)); + } + + /// + /// Returns an indicating the + /// operation produced no value. + /// + /// + /// An indicating the + /// operation produced no value. + /// + public static InputFormatterResult NoValue() + { + return _noValue; + } + + /// + /// Returns a that on completion provides an indicating + /// the operation produced no value. + /// + /// + /// A that on completion provides an indicating the + /// operation produced no value. + /// + public static Task NoValueAsync() + { + return _noValueAsync; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs new file mode 100644 index 0000000000..e42c092624 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.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 System; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// A context object for . + /// + public abstract class OutputFormatterCanWriteContext + { + /// + /// + /// This constructor is obsolete and will be removed in a future version. + /// Please use instead. + /// + /// + /// Creates a new . + /// + /// + [Obsolete("This constructor is obsolete and will be removed in a future version. Please use the constructor taking a HttpContext instead.")] + protected OutputFormatterCanWriteContext() + { + } + + /// + /// Creates a new . + /// + /// The for the current request. + protected OutputFormatterCanWriteContext(HttpContext httpContext) + { + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + HttpContext = httpContext; + } + + /// + /// Gets or sets the context associated with the current operation. + /// + public virtual HttpContext HttpContext { get; protected set; } + + /// + /// Gets or sets the content type to write to the response. + /// + /// + /// An can set this value when its + /// method is called, + /// and expect to see the same value provided in + /// + /// + public virtual StringSegment ContentType { get; set; } + + /// + /// Gets or sets a value to indicate whether the content type was specified by server-side code. + /// This allows to + /// implement stricter filtering on content types that, for example, are being considered purely + /// because of an incoming Accept header. + /// + public virtual bool ContentTypeIsServerDefined { get; set; } + + /// + /// Gets or sets the object to write to the response. + /// + public virtual object Object { get; protected set; } + + /// + /// Gets or sets the of the object to write to the response. + /// + public virtual Type ObjectType { get; protected set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterWriteContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterWriteContext.cs new file mode 100644 index 0000000000..fadcfc03e5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterWriteContext.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 System.IO; +using System.Text; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// A context object for . + /// + public class OutputFormatterWriteContext : OutputFormatterCanWriteContext + { + /// + /// Creates a new . + /// + /// The for the current request. + /// The delegate used to create a for writing the response. + /// The of the object to write to the response. + /// The object to write to the response. + public OutputFormatterWriteContext(HttpContext httpContext, Func writerFactory, Type objectType, object @object) + : base(httpContext) + { + if (writerFactory == null) + { + throw new ArgumentNullException(nameof(writerFactory)); + } + + WriterFactory = writerFactory; + ObjectType = objectType; + Object = @object; + } + + /// + /// + /// Gets or sets a delegate used to create a for writing text to the response. + /// + /// + /// Write to directly to write binary data to the response. + /// + /// + /// + /// + /// The created by this delegate will encode text and write to the + /// stream. Call this delegate to create a + /// for writing text output to the response stream. + /// + /// + /// To implement a formatter that writes binary data to the response stream, do not use the + /// delegate, and use instead. + /// + /// + public virtual Func WriterFactory { get; protected set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/IActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/IActionResult.cs new file mode 100644 index 0000000000..8cfbee8a9c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/IActionResult.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Defines a contract that represents the result of an action method. + /// + public interface IActionResult + { + /// + /// Executes the result operation of the action method asynchronously. This method is called by MVC to process + /// the result of an action method. + /// + /// The context in which the result is executed. The context information includes + /// information about the action that was executed and request information. + /// A task that represents the asynchronous execute operation. + Task ExecuteResultAsync(ActionContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs new file mode 100644 index 0000000000..fc329aadc4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.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.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Defines the contract for the helper to build URLs for ASP.NET MVC within an application. + /// + public interface IUrlHelper + { + /// + /// Gets the for the current request. + /// + ActionContext ActionContext { get; } + + /// + /// 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. + /// + /// The context object for the generated URLs for an action method. + /// The generated URL. + string Action(UrlActionContext actionContext); + + /// + /// Converts a virtual (relative) path to an application absolute path. + /// + /// + /// If the specified content path does not start with the tilde (~) character, + /// this method returns unchanged. + /// + /// The virtual path of the content. + /// The application absolute path. + string Content(string contentPath); + + /// + /// Returns a value that indicates whether the URL is local. A URL is considered local if it does not have a + /// host / authority part and it has an absolute path. URLs using virtual paths ('~/') are also local. + /// + /// The URL. + /// true if the URL is local; otherwise, false. + /// + /// + /// For example, the following URLs are considered local: + /// + /// /Views/Default/Index.html + /// ~/Index.html + /// + /// + /// + /// The following URLs are non-local: + /// + /// ../Index.html + /// http://www.contoso.com/ + /// http://localhost/Index.html + /// + /// + /// + bool IsLocalUrl(string url); + + /// + /// 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. + /// + /// The context object for the generated URLs for a route. + /// The generated URL. + 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. + /// + /// The name of the route that is used to generate URL. + /// An object that contains route values. + /// The generated absolute URL. + string Link(string routeName, object values); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj new file mode 100644 index 0000000000..4ebbaeec61 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj @@ -0,0 +1,22 @@ + + + + ASP.NET Core MVC abstractions and interfaces for action invocation and dispatching, authorization, action filters, formatters, model binding, routing, validation, and more. +Commonly used types: +Microsoft.AspNetCore.Mvc.IActionResult + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;aspnetcoremvc + + + + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingInfo.cs new file mode 100644 index 0000000000..baef377507 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingInfo.cs @@ -0,0 +1,249 @@ +// Copyright (c) .NET 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.AspNetCore.Mvc.ModelBinding +{ + /// + /// Binding info which represents metadata associated to an action parameter. + /// + public class BindingInfo + { + /// + /// Creates a new . + /// + public BindingInfo() + { + } + + /// + /// Creates a copy of a . + /// + /// The to copy. + public BindingInfo(BindingInfo other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + BindingSource = other.BindingSource; + BinderModelName = other.BinderModelName; + BinderType = other.BinderType; + PropertyFilterProvider = other.PropertyFilterProvider; + RequestPredicate = other.RequestPredicate; + } + + /// + /// Gets or sets the . + /// + public BindingSource BindingSource { get; set; } + + /// + /// Gets or sets the binder model name. + /// + public string BinderModelName { get; set; } + + /// + /// Gets or sets the of the model binder used to bind the model. + /// + public Type BinderType { get; set; } + + /// + /// Gets or sets the . + /// + public IPropertyFilterProvider PropertyFilterProvider { get; set; } + + /// + /// Gets or sets a predicate which determines whether or not the model should be bound based on state + /// from the current request. + /// + public Func RequestPredicate { get; set; } + + /// + /// Constructs a new instance of from the given . + /// + /// This overload does not account for specified via . Consider using + /// overload, or + /// on the result of this method to get a more accurate instance. + /// + /// + /// A collection of attributes which are used to construct + /// + /// A new instance of . + public static BindingInfo GetBindingInfo(IEnumerable attributes) + { + var bindingInfo = new BindingInfo(); + var isBindingInfoPresent = false; + + // BinderModelName + foreach (var binderModelNameAttribute in attributes.OfType()) + { + isBindingInfoPresent = true; + if (binderModelNameAttribute?.Name != null) + { + bindingInfo.BinderModelName = binderModelNameAttribute.Name; + break; + } + } + + // BinderType + foreach (var binderTypeAttribute in attributes.OfType()) + { + isBindingInfoPresent = true; + if (binderTypeAttribute.BinderType != null) + { + bindingInfo.BinderType = binderTypeAttribute.BinderType; + break; + } + } + + // BindingSource + foreach (var bindingSourceAttribute in attributes.OfType()) + { + isBindingInfoPresent = true; + if (bindingSourceAttribute.BindingSource != null) + { + bindingInfo.BindingSource = bindingSourceAttribute.BindingSource; + break; + } + } + + // PropertyFilterProvider + var propertyFilterProviders = attributes.OfType().ToArray(); + if (propertyFilterProviders.Length == 1) + { + isBindingInfoPresent = true; + bindingInfo.PropertyFilterProvider = propertyFilterProviders[0]; + } + else if (propertyFilterProviders.Length > 1) + { + isBindingInfoPresent = true; + bindingInfo.PropertyFilterProvider = new CompositePropertyFilterProvider(propertyFilterProviders); + } + + // RequestPredicate + foreach (var requestPredicateProvider in attributes.OfType()) + { + isBindingInfoPresent = true; + if (requestPredicateProvider.RequestPredicate != null) + { + bindingInfo.RequestPredicate = requestPredicateProvider.RequestPredicate; + break; + } + } + + return isBindingInfoPresent ? bindingInfo : null; + } + + /// + /// Constructs a new instance of from the given and . + /// + /// A collection of attributes which are used to construct . + /// The . + /// A new instance of if any binding metadata was discovered; otherwise or . + public static BindingInfo GetBindingInfo(IEnumerable attributes, ModelMetadata modelMetadata) + { + if (attributes == null) + { + throw new ArgumentNullException(nameof(attributes)); + } + + if (modelMetadata == null) + { + throw new ArgumentNullException(nameof(modelMetadata)); + } + + var bindingInfo = GetBindingInfo(attributes); + var isBindingInfoPresent = bindingInfo != null; + + if (bindingInfo == null) + { + bindingInfo = new BindingInfo(); + } + + isBindingInfoPresent |= bindingInfo.TryApplyBindingInfo(modelMetadata); + + return isBindingInfoPresent ? bindingInfo : null; + } + + /// + /// Applies binding metadata from the specified . + /// + /// Uses values from if no value is already available. + /// + /// + /// The . + /// if any binding metadata from was applied; + /// otherwise. + public bool TryApplyBindingInfo(ModelMetadata modelMetadata) + { + if (modelMetadata == null) + { + throw new ArgumentNullException(nameof(modelMetadata)); + } + + var isBindingInfoPresent = false; + if (BinderModelName == null && modelMetadata.BinderModelName != null) + { + isBindingInfoPresent = true; + BinderModelName = modelMetadata.BinderModelName; + } + + if (BinderType == null && modelMetadata.BinderType != null) + { + isBindingInfoPresent = true; + BinderType = modelMetadata.BinderType; + } + + if (BindingSource == null && modelMetadata.BindingSource != null) + { + isBindingInfoPresent = true; + BindingSource = modelMetadata.BindingSource; + } + + if (PropertyFilterProvider == null && modelMetadata.PropertyFilterProvider != null) + { + isBindingInfoPresent = true; + PropertyFilterProvider = modelMetadata.PropertyFilterProvider; + } + + return isBindingInfoPresent; + } + + private class CompositePropertyFilterProvider : IPropertyFilterProvider + { + private readonly IEnumerable _providers; + + public CompositePropertyFilterProvider(IEnumerable providers) + { + _providers = providers; + } + + public Func PropertyFilter => CreatePropertyFilter(); + + private Func CreatePropertyFilter() + { + var propertyFilters = _providers + .Select(p => p.PropertyFilter) + .Where(p => p != null); + + return (m) => + { + foreach (var propertyFilter in propertyFilters) + { + if (!propertyFilter(m)) + { + return false; + } + } + + return true; + }; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.cs new file mode 100644 index 0000000000..995ba57c4d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.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.Diagnostics; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A metadata object representing a source of data for model binding. + /// + [DebuggerDisplay("Source: {DisplayName}")] + public class BindingSource : IEquatable + { + /// + /// A for the request body. + /// + public static readonly BindingSource Body = new BindingSource( + "Body", + Resources.BindingSource_Body, + isGreedy: true, + isFromRequest: true); + + /// + /// A for a custom model binder (unknown data source). + /// + public static readonly BindingSource Custom = new BindingSource( + "Custom", + Resources.BindingSource_Custom, + isGreedy: true, + isFromRequest: true); + + /// + /// A for the request form-data. + /// + public static readonly BindingSource Form = new BindingSource( + "Form", + Resources.BindingSource_Form, + isGreedy: false, + isFromRequest: true); + + /// + /// A for the request headers. + /// + public static readonly BindingSource Header = new BindingSource( + "Header", + Resources.BindingSource_Header, + isGreedy: true, + isFromRequest: true); + + /// + /// A for model binding. Includes form-data, query-string + /// and route data from the request. + /// + public static readonly BindingSource ModelBinding = new BindingSource( + "ModelBinding", + Resources.BindingSource_ModelBinding, + isGreedy: false, + isFromRequest: true); + + /// + /// A for the request url path. + /// + public static readonly BindingSource Path = new BindingSource( + "Path", + Resources.BindingSource_Path, + isGreedy: false, + isFromRequest: true); + + /// + /// A for the request query-string. + /// + public static readonly BindingSource Query = new BindingSource( + "Query", + Resources.BindingSource_Query, + isGreedy: false, + isFromRequest: true); + + /// + /// A for request services. + /// + public static readonly BindingSource Services = new BindingSource( + "Services", + Resources.BindingSource_Services, + isGreedy: true, + isFromRequest: false); + + /// + /// A for special parameter types that are not user input. + /// + public static readonly BindingSource Special = new BindingSource( + "Special", + Resources.BindingSource_Special, + isGreedy: true, + isFromRequest: false); + + /// + /// A for , , and . + /// + public static readonly BindingSource FormFile = new BindingSource( + "FormFile", + Resources.BindingSource_FormFile, + isGreedy: true, + isFromRequest: true); + + /// + /// Creates a new . + /// + /// The id, a unique identifier. + /// The display name. + /// A value indicating whether or not the source is greedy. + /// + /// A value indicating whether or not the data comes from the HTTP request. + /// + public BindingSource(string id, string displayName, bool isGreedy, bool isFromRequest) + { + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + + Id = id; + DisplayName = displayName; + IsGreedy = isGreedy; + IsFromRequest = isFromRequest; + } + + /// + /// Gets the display name for the source. + /// + public string DisplayName { get; } + + /// + /// Gets the unique identifier for the source. Sources are compared based on their Id. + /// + public string Id { get; } + + /// + /// Gets a value indicating whether or not a source is greedy. A greedy source will bind a model in + /// a single operation, and will not decompose the model into sub-properties. + /// + /// + /// + /// For sources based on a , setting to false + /// will most closely describe the behavior. This value is used inside the default model binders to + /// determine whether or not to attempt to bind properties of a model. + /// + /// + /// Set to true for most custom implementations. + /// + /// + /// If a source represents an which will recursively traverse a model's properties + /// and bind them individually using , then set to + /// true. + /// + /// + public bool IsGreedy { get; } + + /// + /// Gets a value indicating whether or not the binding source uses input from the current HTTP request. + /// + /// + /// Some sources (like ) are based on application state and not user + /// input. These are excluded by default from ApiExplorer diagnostics. + /// + public bool IsFromRequest { get; } + + /// + /// Gets a value indicating whether or not the can accept + /// data from . + /// + /// The to consider as input. + /// True if the source is compatible, otherwise false. + /// + /// When using this method, it is expected that the left-hand-side is metadata specified + /// on a property or parameter for model binding, and the right hand side is a source of + /// data used by a model binder or value provider. + /// + /// This distinction is important as the left-hand-side may be a composite, but the right + /// may not. + /// + public virtual bool CanAcceptDataFrom(BindingSource bindingSource) + { + if (bindingSource == null) + { + throw new ArgumentNullException(nameof(bindingSource)); + } + + if (bindingSource is CompositeBindingSource) + { + var message = Resources.FormatBindingSource_CannotBeComposite( + bindingSource.DisplayName, + nameof(CanAcceptDataFrom)); + throw new ArgumentException(message, nameof(bindingSource)); + } + + return this == bindingSource; + } + + /// + public bool Equals(BindingSource other) + { + return string.Equals(other?.Id, Id, StringComparison.Ordinal); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as BindingSource); + } + + /// + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + /// + public static bool operator ==(BindingSource s1, BindingSource s2) + { + if (object.ReferenceEquals(s1, null)) + { + return object.ReferenceEquals(s2, null); + } + + return s1.Equals(s2); + } + + /// + public static bool operator !=(BindingSource s1, BindingSource s2) + { + return !(s1 == s2); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/CompositeBindingSource.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/CompositeBindingSource.cs new file mode 100644 index 0000000000..886deb9951 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/CompositeBindingSource.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 System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A which can represent multiple value-provider data sources. + /// + public class CompositeBindingSource : BindingSource + { + /// + /// Creates a new . + /// + /// + /// The set of entries. + /// Must be value-provider sources and user input. + /// + /// The display name for the composite source. + /// A . + public static CompositeBindingSource Create( + IEnumerable bindingSources, + string displayName) + { + if (bindingSources == null) + { + throw new ArgumentNullException(nameof(bindingSources)); + } + + foreach (var bindingSource in bindingSources) + { + if (bindingSource.IsGreedy) + { + var message = Resources.FormatBindingSource_CannotBeGreedy( + bindingSource.DisplayName, + nameof(CompositeBindingSource)); + throw new ArgumentException(message, nameof(bindingSources)); + } + + if (!bindingSource.IsFromRequest) + { + var message = Resources.FormatBindingSource_MustBeFromRequest( + bindingSource.DisplayName, + nameof(CompositeBindingSource)); + throw new ArgumentException(message, nameof(bindingSources)); + } + + if (bindingSource is CompositeBindingSource) + { + var message = Resources.FormatBindingSource_CannotBeComposite( + bindingSource.DisplayName, + nameof(CompositeBindingSource)); + throw new ArgumentException(message, nameof(bindingSources)); + } + } + + var id = string.Join("&", bindingSources.Select(s => s.Id).OrderBy(s => s, StringComparer.Ordinal)); + return new CompositeBindingSource(id, displayName, bindingSources); + } + + private CompositeBindingSource( + string id, + string displayName, + IEnumerable bindingSources) + : base(id, displayName, isGreedy: false, isFromRequest: true) + { + if (id == null) + { + throw new ArgumentNullException(nameof(id)); + } + + if (bindingSources == null) + { + throw new ArgumentNullException(nameof(bindingSources)); + } + + BindingSources = bindingSources; + } + + /// + /// Gets the set of entries. + /// + public IEnumerable BindingSources { get; } + + /// + public override bool CanAcceptDataFrom(BindingSource bindingSource) + { + if (bindingSource == null) + { + throw new ArgumentNullException(nameof(bindingSource)); + } + + if (bindingSource is CompositeBindingSource) + { + var message = Resources.FormatBindingSource_CannotBeComposite( + bindingSource.DisplayName, + nameof(CanAcceptDataFrom)); + throw new ArgumentException(message, nameof(bindingSource)); + } + + foreach (var source in BindingSources) + { + if (source.CanAcceptDataFrom(bindingSource)) + { + return true; + } + } + + return false; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs new file mode 100644 index 0000000000..0ad0117e5d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/EnumGroupAndName.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An abstraction used when grouping enum values for . + /// + public struct EnumGroupAndName + { + private readonly Func _name; + + /// + /// Initializes a new instance of the structure. This constructor should + /// not be used in any site where localization is important. + /// + /// The group name. + /// The name. + public EnumGroupAndName(string group, string name) + { + if (group == null) + { + throw new ArgumentNullException(nameof(group)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + Group = group; + _name = () => name; + } + + /// + /// Initializes a new instance of the structure. + /// + /// The group name. + /// A which will return the name. + public EnumGroupAndName( + string group, + Func name) + { + if (group == null) + { + throw new ArgumentNullException(nameof(group)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + Group = group; + _name = name; + } + + /// + /// Gets the Group name. + /// + public string Group { get; } + + /// + /// Gets the name. + /// + public string Name => _name(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBinderTypeProviderMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBinderTypeProviderMetadata.cs new file mode 100644 index 0000000000..75740bba00 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBinderTypeProviderMetadata.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Provides a which implements . + /// + public interface IBinderTypeProviderMetadata : IBindingSourceMetadata + { + /// + /// A which implements either . + /// + Type BinderType { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBindingSourceMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBindingSourceMetadata.cs new file mode 100644 index 0000000000..4a1ed31275 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBindingSourceMetadata.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.ModelBinding +{ + /// + /// Metadata which specifies the data source for model binding. + /// + public interface IBindingSourceMetadata + { + /// + /// Gets the . + /// + /// + /// The is metadata which can be used to determine which data + /// sources are valid for model binding of a property or parameter. + /// + BindingSource BindingSource { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinder.cs new file mode 100644 index 0000000000..67cfb95218 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinder.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Defines an interface for model binders. + /// + public interface IModelBinder + { + /// + /// Attempts to bind a model. + /// + /// The . + /// + /// + /// A which will complete when the model binding process completes. + /// + /// + /// If model binding was successful, the should have + /// set to true. + /// + /// + /// A model binder that completes successfully should set to + /// a value returned from . + /// + /// + Task BindModelAsync(ModelBindingContext bindingContext); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinderProvider.cs new file mode 100644 index 0000000000..397fa7e637 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinderProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Creates instances. Register + /// instances in MvcOptions. + /// + public interface IModelBinderProvider + { + /// + /// Creates a based on . + /// + /// The . + /// An . + IModelBinder GetBinder(ModelBinderProviderContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelMetadataProvider.cs new file mode 100644 index 0000000000..32d41f717c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelMetadataProvider.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; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + public interface IModelMetadataProvider + { + ModelMetadata GetMetadataForType(Type modelType); + + IEnumerable GetMetadataForProperties(Type modelType); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelNameProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelNameProvider.cs new file mode 100644 index 0000000000..321b7af174 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelNameProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Represents an entity which can provide model name as metadata. + /// + public interface IModelNameProvider + { + /// + /// Model name. + /// + string Name { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IPropertyFilterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IPropertyFilterProvider.cs new file mode 100644 index 0000000000..76ddd463cc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IPropertyFilterProvider.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Provides a predicate which can determines which model properties should be bound by model binding. + /// + public interface IPropertyFilterProvider + { + /// + /// Gets a predicate which can determines which model properties should be bound by model binding. + /// + Func PropertyFilter { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IRequestPredicateProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IRequestPredicateProvider.cs new file mode 100644 index 0000000000..ab72771f22 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IRequestPredicateProvider.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 Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An interface that allows a top-level model to be bound or not bound based on state associated + /// with the current request. + /// + public interface IRequestPredicateProvider + { + /// + /// Gets a function which determines whether or not the model object should be bound based + /// on the current request. + /// + Func RequestPredicate { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProvider.cs new file mode 100644 index 0000000000..4e57509298 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Defines the methods that are required for a value provider. + /// + public interface IValueProvider + { + /// + /// Determines whether the collection contains the specified prefix. + /// + /// The prefix to search for. + /// true if the collection contains the specified prefix; otherwise, false. + bool ContainsPrefix(string prefix); + + /// + /// Retrieves a value object using the specified key. + /// + /// The key of the value object to retrieve. + /// The value object for the specified key. If the exact key is not found, null. + ValueProviderResult GetValue(string key); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProviderFactory.cs new file mode 100644 index 0000000000..39b2948fe7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProviderFactory.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A factory for creating instances. + /// + public interface IValueProviderFactory + { + /// + /// Creates a with values from the current request + /// and adds it to list. + /// + /// The . + /// A that when completed will add an instance + /// to list if applicable. + Task CreateValueProviderAsync(ValueProviderFactoryContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelBindingMessageProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelBindingMessageProvider.cs new file mode 100644 index 0000000000..5ab37cfd8b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelBindingMessageProvider.cs @@ -0,0 +1,95 @@ +// Copyright (c) .NET 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.ModelBinding.Metadata +{ + /// + /// Provider for error messages the model binding system detects. + /// + public abstract class ModelBindingMessageProvider + { + /// + /// 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.". + public virtual Func MissingBindRequiredValueAccessor { get; } + + /// + /// Error message the model binding system adds when either the key or the value of a + /// is bound but not both. + /// + /// Default is "A value is required.". + public virtual Func MissingKeyOrValueAccessor { get; } + + /// + /// Error message the model binding system adds when no value is provided for the request body, + /// but a value is required. + /// + /// Default is "A non-empty request body is required.". + public virtual Func MissingRequestBodyRequiredValueAccessor { get; } + + /// + /// Error message the model binding system adds when a null value is bound to a + /// non- property. + /// + /// Default is "The value '{0}' is invalid.". + public virtual Func ValueMustNotBeNullAccessor { get; } + + /// + /// Error message the model binding system adds when is of type + /// or , value is known, and error is associated + /// with a property. + /// + /// Default is "The value '{0}' is not valid for {1}.". + public virtual Func AttemptedValueIsInvalidAccessor { get; } + + /// + /// Error message the model binding system adds when is of type + /// or , value is known, and error is associated + /// with a collection element or action parameter. + /// + /// Default is "The value '{0}' is not valid.". + public virtual Func NonPropertyAttemptedValueIsInvalidAccessor { get; } + + /// + /// Error message the model binding system adds when is of type + /// or , value is unknown, and error is associated + /// with a property. + /// + /// Default is "The supplied value is invalid for {0}.". + public virtual Func UnknownValueIsInvalidAccessor { get; } + + /// + /// Error message the model binding system adds when is of type + /// or , value is unknown, and error is associated + /// with a collection element or action parameter. + /// + /// Default is "The supplied value is invalid.". + public virtual Func NonPropertyUnknownValueIsInvalidAccessor { get; } + + /// + /// Fallback error message HTML and tag helpers display when a property is invalid but the + /// s have null s. + /// + /// Default is "The value '{0}' is invalid.". + public virtual Func ValueIsInvalidAccessor { get; } + + /// + /// Error message HTML and tag helpers add for client-side validation of numeric formats. Visible in the + /// browser if the field for a float (for example) property does not have a correctly-formatted value. + /// + /// Default is "The field {0} must be a number.". + public virtual Func ValueMustBeANumberAccessor { get; } + + /// + /// Error message HTML and tag helpers add for client-side validation of numeric formats. Visible in the + /// browser if the field for a float (for example) collection element or action parameter does not have a + /// correctly-formatted value. + /// + /// Default is "The field must be a number.". + public virtual Func NonPropertyValueMustBeANumberAccessor { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs new file mode 100644 index 0000000000..913576e6a8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs @@ -0,0 +1,177 @@ +// Copyright (c) .NET 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.Abstractions; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// A key type which identifies a . + /// + public struct ModelMetadataIdentity : IEquatable + { + /// + /// Creates a for the provided model . + /// + /// The model . + /// A . + public static ModelMetadataIdentity ForType(Type modelType) + { + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + return new ModelMetadataIdentity() + { + ModelType = modelType, + }; + } + + /// + /// Creates a for the provided property. + /// + /// The model type. + /// The name of the property. + /// The container type of the model property. + /// A . + public static ModelMetadataIdentity ForProperty( + Type modelType, + string name, + Type containerType) + { + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + if (containerType == null) + { + throw new ArgumentNullException(nameof(containerType)); + } + + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(name)); + } + + return new ModelMetadataIdentity() + { + ModelType = modelType, + Name = name, + ContainerType = containerType, + }; + } + + /// + /// 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 . + private 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 = modelType, + 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; } + + /// + /// Gets the represented by the current instance. + /// + public Type ModelType { get; private set; } + + /// + /// Gets a value indicating the kind of metadata represented by the current instance. + /// + public ModelMetadataKind MetadataKind + { + get + { + if (ParameterInfo != null) + { + return ModelMetadataKind.Parameter; + } + else if (ContainerType != null && Name != null) + { + return ModelMetadataKind.Property; + } + else + { + return ModelMetadataKind.Type; + } + } + } + + /// + /// 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; } + + /// + /// Gets a descriptor for the parameter, or null if this instance + /// does not represent a parameter. + /// + public ParameterInfo ParameterInfo { get; private set; } + + /// + public bool Equals(ModelMetadataIdentity other) + { + return + ContainerType == other.ContainerType && + ModelType == other.ModelType && + Name == other.Name && + ParameterInfo == other.ParameterInfo; + } + + /// + public override bool Equals(object obj) + { + var other = obj as ModelMetadataIdentity?; + return other.HasValue && Equals(other.Value); + } + + /// + public override int GetHashCode() + { + var hash = new HashCodeCombiner(); + hash.Add(ContainerType); + hash.Add(ModelType); + hash.Add(Name, StringComparer.Ordinal); + hash.Add(ParameterInfo); + return hash; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataKind.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataKind.cs new file mode 100644 index 0000000000..4756f85226 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataKind.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// Enumeration for the kinds of + /// + public enum ModelMetadataKind + { + /// + /// Used for for a . + /// + Type, + + /// + /// Used for for a property. + /// + Property, + + /// + /// Used for for a parameter. + /// + Parameter, + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBinderProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBinderProviderContext.cs new file mode 100644 index 0000000000..6228923e70 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBinderProviderContext.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A context object for . + /// + public abstract class ModelBinderProviderContext + { + /// + /// Creates an for the given . + /// + /// The for the model. + /// An . + public abstract IModelBinder CreateBinder(ModelMetadata metadata); + + /// + /// Creates an for the given + /// and . + /// + /// The for the model. + /// The that should be used + /// for creating the binder. + /// An . + public virtual IModelBinder CreateBinder(ModelMetadata metadata, BindingInfo bindingInfo) + { + throw new NotSupportedException(); + } + + /// + /// Gets the . + /// + public abstract BindingInfo BindingInfo { get; } + + /// + /// Gets the . + /// + public abstract ModelMetadata Metadata { get; } + + /// + /// Gets the . + /// + public abstract IModelMetadataProvider MetadataProvider { get; } + + /// + /// Gets the . + /// + public virtual IServiceProvider Services { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs new file mode 100644 index 0000000000..50c0149cc2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs @@ -0,0 +1,181 @@ +// Copyright (c) .NET 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.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A context that contains operating information for model binding and validation. + /// + public abstract class ModelBindingContext + { + /// + /// Represents the associated with this context. + /// + /// + /// The property setter is provided for unit testing purposes only. + /// + public abstract ActionContext ActionContext { get; set; } + + /// + /// Gets or sets a model name which is explicitly set using an . + /// + public abstract string BinderModelName { get; set; } + + /// + /// Gets or sets a value which represents the associated with the + /// . + /// + public abstract BindingSource BindingSource { get; set; } + + /// + /// Gets or sets the name of the current field being bound. + /// + public abstract string FieldName { get; set; } + + /// + /// Gets the associated with this context. + /// + public virtual HttpContext HttpContext => ActionContext?.HttpContext; + + /// + /// Gets or sets an indication that the current binder is handling the top-level object. + /// + /// Passed into the model binding system. + public abstract bool IsTopLevelObject { get; set; } + + /// + /// Gets or sets the model value for the current operation. + /// + /// + /// The will typically be set for a binding operation that works + /// against a pre-existing model object to update certain properties. + /// + public abstract object Model { get; set; } + + /// + /// Gets or sets the metadata for the model associated with this context. + /// + public abstract ModelMetadata ModelMetadata { get; set; } + + /// + /// Gets or sets the name of the model. This property is used as a key for looking up values in + /// during model binding. + /// + public abstract string ModelName { get; set; } + + /// + /// Gets or sets the used to capture values + /// for properties in the object graph of the model when binding. + /// + /// + /// The property setter is provided for unit testing purposes only. + /// + public abstract ModelStateDictionary ModelState { get; set; } + + /// + /// Gets the type of the model. + /// + /// + /// The property must be set to access this property. + /// + public virtual Type ModelType => ModelMetadata.ModelType; + + /// + /// Gets or sets a predicate which will be evaluated for each property to determine if the property + /// is eligible for model binding. + /// + public abstract Func PropertyFilter { get; set; } + + /// + /// Gets or sets the . Used for tracking validation state to + /// customize validation behavior for a model object. + /// + /// + /// The property setter is provided for unit testing purposes only. + /// + public abstract ValidationStateDictionary ValidationState { get; set; } + + /// + /// Gets or sets the associated with this context. + /// + public abstract IValueProvider ValueProvider { get; set; } + + /// + /// + /// Gets or sets a which represents the result of the model binding process. + /// + /// + /// Before an is called, will be set to a value indicating + /// failure. The binder should set to a value created with + /// if model binding succeeded. + /// + /// + public abstract ModelBindingResult Result { get; set; } + + /// + /// Pushes a layer of state onto this context. Model binders will call this as part of recursion when binding + /// properties or collection items. + /// + /// + /// to assign to the property. + /// + /// Name to assign to the property. + /// Name to assign to the property. + /// Instance to assign to the property. + /// + /// A scope object which should be used in a using statement where + /// is called. + /// + public abstract NestedScope EnterNestedScope( + ModelMetadata modelMetadata, + string fieldName, + string modelName, + object model); + + /// + /// Pushes a layer of state onto this context. Model binders will call this as part of recursion when binding + /// properties or collection items. + /// + /// + /// A scope object which should be used in a using statement where + /// is called. + /// + public abstract NestedScope EnterNestedScope(); + + /// + /// Removes a layer of state pushed by calling . + /// + protected abstract void ExitNestedScope(); + + /// + /// Return value of . Should be disposed + /// by caller when child binding context state should be popped off of + /// the . + /// + public struct NestedScope : IDisposable + { + private readonly ModelBindingContext _context; + + /// + /// Initializes the for a . + /// + /// + public NestedScope(ModelBindingContext context) + { + _context = context; + } + + /// + /// Exits the created by calling . + /// + public void Dispose() + { + _context.ExitNestedScope(); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingResult.cs new file mode 100644 index 0000000000..de57b8bf88 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingResult.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 Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Contains the result of model binding. + /// + public struct ModelBindingResult : IEquatable + { + /// + /// Creates a representing a failed model binding operation. + /// + /// A representing a failed model binding operation. + public static ModelBindingResult Failed() + { + return new ModelBindingResult(model: null, isModelSet: false); + } + + /// + /// Creates a representing a successful model binding operation. + /// + /// The model value. May be null. + /// A representing a successful model bind. + public static ModelBindingResult Success(object model) + { + return new ModelBindingResult( model, isModelSet: true); + } + + private ModelBindingResult(object model, bool isModelSet) + { + Model = model; + IsModelSet = isModelSet; + } + + /// + /// Gets the model associated with this context. + /// + public object Model { get; } + + /// + /// + /// Gets a value indicating whether or not the value has been set. + /// + /// + /// This property can be used to distinguish between a model binder which does not find a value and + /// the case where a model binder sets the null value. + /// + /// + public bool IsModelSet { get; } + + /// + public override bool Equals(object obj) + { + var other = obj as ModelBindingResult?; + if (other == null) + { + return false; + } + else + { + return Equals(other.Value); + } + } + + /// + public override int GetHashCode() + { + var hashCodeCombiner = HashCodeCombiner.Start(); + hashCodeCombiner.Add(IsModelSet); + hashCodeCombiner.Add(Model); + + return hashCodeCombiner.CombinedHash; + } + + /// + public bool Equals(ModelBindingResult other) + { + return + IsModelSet == other.IsModelSet && + object.Equals(Model, other.Model); + } + + /// + public override string ToString() + { + if (IsModelSet) + { + return $"Success '{Model}'"; + } + else + { + return "Failed"; + } + } + + /// + /// Compares objects for equality. + /// + /// A . + /// A . + /// true if the objects are equal, otherwise false. + public static bool operator ==(ModelBindingResult x, ModelBindingResult y) + { + return x.Equals(y); + } + + /// + /// Compares objects for inequality. + /// + /// A . + /// A . + /// true if the objects are not equal, otherwise false. + public static bool operator !=(ModelBindingResult x, ModelBindingResult y) + { + return !x.Equals(y); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelError.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelError.cs new file mode 100644 index 0000000000..b2125e206d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelError.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + public class ModelError + { + public ModelError(Exception exception) + : this(exception, errorMessage: null) + { + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + } + + public ModelError(Exception exception, string errorMessage) + : this(errorMessage) + { + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + Exception = exception; + } + + public ModelError(string errorMessage) + { + ErrorMessage = errorMessage ?? string.Empty; + } + + public Exception Exception { get; } + + public string ErrorMessage { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelErrorCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelErrorCollection.cs new file mode 100644 index 0000000000..b8d2e95fb0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelErrorCollection.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; +using System.Collections.ObjectModel; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + public class ModelErrorCollection : Collection + { + public void Add(Exception exception) + { + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + Add(new ModelError(exception)); + } + + public void Add(string errorMessage) + { + if (errorMessage == null) + { + throw new ArgumentNullException(nameof(errorMessage)); + } + + Add(new ModelError(errorMessage)); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs new file mode 100644 index 0000000000..f78815a7d1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs @@ -0,0 +1,510 @@ +// Copyright (c) .NET 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.ComponentModel; +using System.Diagnostics; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A metadata representation of a model type, property or parameter. + /// + [DebuggerDisplay("{DebuggerToString(),nq}")] + public abstract class ModelMetadata : IEquatable, IModelMetadataProvider + { + /// + /// The default value of . + /// + public static readonly int DefaultOrder = 10000; + + private int? _hashCode; + + /// + /// Creates a new . + /// + /// The . + protected ModelMetadata(ModelMetadataIdentity identity) + { + Identity = identity; + + InitializeTypeInformation(); + } + + /// + /// Gets the type containing the property if this metadata is for a property; otherwise. + /// + public Type ContainerType => Identity.ContainerType; + + /// + /// Gets the metadata for if this metadata is for a property; + /// otherwise. + /// + public virtual ModelMetadata ContainerMetadata + { + get + { + throw new NotImplementedException(); + } + } + + /// + /// Gets a value indicating the kind of metadata element represented by the current instance. + /// + public ModelMetadataKind MetadataKind => Identity.MetadataKind; + + /// + /// Gets the model type represented by the current instance. + /// + public Type ModelType => Identity.ModelType; + + /// + /// Gets the name of the parameter or property if this metadata is for a parameter or property; + /// otherwise i.e. if this is the metadata for a type. + /// + public string Name => Identity.Name; + + /// + /// Gets the name of the parameter if this metadata is for a parameter; otherwise. + /// + public string ParameterName => MetadataKind == ModelMetadataKind.Parameter ? Identity.Name : null; + + /// + /// Gets the name of the property if this metadata is for a property; otherwise. + /// + public string PropertyName => MetadataKind == ModelMetadataKind.Property ? Identity.Name : null; + + /// + /// Gets the key for the current instance. + /// + protected ModelMetadataIdentity Identity { get; } + + /// + /// Gets a collection of additional information about the model. + /// + public abstract IReadOnlyDictionary AdditionalValues { get; } + + /// + /// Gets the collection of instances for the model's properties. + /// + public abstract ModelPropertyCollection Properties { get; } + + /// + /// Gets the name of a model if specified explicitly using . + /// + public abstract string BinderModelName { get; } + + /// + /// Gets the of an of a model if specified explicitly using + /// . + /// + public abstract Type BinderType { get; } + + /// + /// Gets a binder metadata for this model. + /// + public abstract BindingSource BindingSource { get; } + + /// + /// Gets a value indicating whether or not to convert an empty string value or one containing only whitespace + /// characters to null when representing a model as text. + /// + public abstract bool ConvertEmptyStringToNull { get; } + + /// + /// Gets the name of the model's datatype. Overrides in some + /// display scenarios. + /// + /// null unless set manually or through additional metadata e.g. attributes. + public abstract string DataTypeName { get; } + + /// + /// Gets the description of the model. + /// + public abstract string Description { get; } + + /// + /// Gets the format string (see https://msdn.microsoft.com/en-us/library/txafckwd.aspx) used to display the + /// model. + /// + public abstract string DisplayFormatString { get; } + + /// + /// Gets the display name of the model. + /// + public abstract string DisplayName { get; } + + /// + /// Gets the format string (see https://msdn.microsoft.com/en-us/library/txafckwd.aspx) used to edit the model. + /// + public abstract string EditFormatString { get; } + + /// + /// Gets the for elements of if that + /// implements . + /// + /// + /// for T if implements + /// . for object if + /// implements but not . null otherwise i.e. when + /// is false. + /// + public abstract ModelMetadata ElementMetadata { get; } + + /// + /// Gets the ordered and grouped display names and values of all values in + /// . + /// + /// + /// An of of mappings between + /// field groups, names and values. null if is false. + /// + public abstract IEnumerable> EnumGroupedDisplayNamesAndValues { get; } + + /// + /// Gets the names and values of all values in . + /// + /// + /// An of mappings between field names + /// and values. null if is false. + /// + public abstract IReadOnlyDictionary EnumNamesAndValues { get; } + + /// + /// Gets a value indicating whether has a non-null, non-empty + /// value different from the default for the datatype. + /// + public abstract bool HasNonDefaultEditFormat { get; } + + /// + /// Gets a value indicating whether the value should be HTML-encoded. + /// + /// If true, value should be HTML-encoded. Default is true. + public abstract bool HtmlEncode { get; } + + /// + /// Gets a value indicating whether the "HiddenInput" display template should return + /// string.Empty (not the expression value) and whether the "HiddenInput" editor template should not + /// also return the expression value (together with the hidden <input> element). + /// + /// + /// If true, also causes the default display and editor templates to return HTML + /// lacking the usual per-property <div> wrapper around the associated property. Thus the default + /// display template effectively skips the property and the default + /// editor template returns only the hidden <input> element for the property. + /// + public abstract bool HideSurroundingHtml { get; } + + /// + /// Gets a value indicating whether or not the model value can be bound by model binding. This is only + /// applicable when the current instance represents a property. + /// + /// + /// If true then the model value is considered supported by model binding and can be set + /// based on provided input in the request. + /// + public abstract bool IsBindingAllowed { get; } + + /// + /// Gets a value indicating whether or not the model value is required by model binding. This is only + /// applicable when the current instance represents a property. + /// + /// + /// If true then the model value is considered required by model binding and must have a value + /// supplied in the request to be considered valid. + /// + public abstract bool IsBindingRequired { get; } + + /// + /// Gets a value indicating whether is for an . + /// + /// + /// true if type.IsEnum (type.GetTypeInfo().IsEnum for DNX Core 5.0) is true for + /// ; false otherwise. + /// + public abstract bool IsEnum { get; } + + /// + /// Gets a value indicating whether is for an with an + /// associated . + /// + /// + /// true if is true and has an + /// associated ; false otherwise. + /// + public abstract bool IsFlagsEnum { get; } + + /// + /// Gets a value indicating whether or not the model value is read-only. This is only applicable when + /// the current instance represents a property. + /// + public abstract bool IsReadOnly { get; } + + /// + /// Gets a value indicating whether or not the model value is required. This is only applicable when + /// the current instance represents a property. + /// + /// + /// + /// If true then the model value is considered required by validators. + /// + /// + /// By default an implicit System.ComponentModel.DataAnnotations.RequiredAttribute will be added + /// if not present when true.. + /// + /// + public abstract bool IsRequired { get; } + + /// + /// Gets the instance. + /// + public abstract ModelBindingMessageProvider ModelBindingMessageProvider { get; } + + /// + /// Gets a value indicating where the current metadata should be ordered relative to other properties + /// in its containing type. + /// + /// + /// For example this property is used to order items in . + /// The default order is 10000. + /// + /// The order value of the current metadata. + public abstract int Order { get; } + + /// + /// Gets the text to display as a placeholder value for an editor. + /// + public abstract string Placeholder { get; } + + /// + /// Gets the text to display when the model is null. + /// + public abstract string NullDisplayText { get; } + + /// + /// Gets the , which can determine which properties + /// should be model bound. + /// + public abstract IPropertyFilterProvider PropertyFilterProvider { get; } + + /// + /// Gets a value that indicates whether the property should be displayed in read-only views. + /// + public abstract bool ShowForDisplay { get; } + + /// + /// Gets a value that indicates whether the property should be displayed in editable views. + /// + public abstract bool ShowForEdit { get; } + + /// + /// Gets a value which is the name of the property used to display the model. + /// + public abstract string SimpleDisplayProperty { get; } + + /// + /// Gets a string used by the templating system to discover display-templates and editor-templates. + /// + public abstract string TemplateHint { get; } + + /// + /// Gets an implementation that indicates whether this model should be + /// validated. If null, properties with this are validated. + /// + /// Defaults to null. + public virtual IPropertyValidationFilter PropertyValidationFilter => null; + + /// + /// Gets a value that indicates whether properties or elements of the model should be validated. + /// + public abstract bool ValidateChildren { get; } + + /// + /// Gets a collection of metadata items for validators. + /// + public abstract IReadOnlyList ValidatorMetadata { get; } + + /// + /// Gets the for elements of if that + /// implements . + /// + public Type ElementType { get; private set; } + + /// + /// Gets a value indicating whether is a complex type. + /// + /// + /// A complex type is defined as a which has a + /// that can convert from . + /// + public bool IsComplexType { get; private set; } + + /// + /// Gets a value indicating whether or not is a . + /// + public bool IsNullableValueType { get; private set; } + + /// + /// Gets a value indicating whether or not is a collection type. + /// + /// + /// A collection type is defined as a which is assignable to . + /// + public bool IsCollectionType { get; private set; } + + /// + /// Gets a value indicating whether or not is an enumerable type. + /// + /// + /// An enumerable type is defined as a which is assignable to + /// , and is not a . + /// + public bool IsEnumerableType { get; private set; } + + /// + /// Gets a value indicating whether or not allows null values. + /// + public bool IsReferenceOrNullableType { get; private set; } + + /// + /// Gets the underlying type argument if inherits from . + /// Otherwise gets . + /// + /// + /// Identical to unless is true. + /// + public Type UnderlyingOrModelType { get; private set; } + + /// + /// Gets a property getter delegate to get the property value from a model object. + /// + public abstract Func PropertyGetter { get; } + + /// + /// Gets a property setter delegate to set the property value on a model object. + /// + public abstract Action PropertySetter { get; } + + /// + /// Gets a display name for the model. + /// + /// + /// will return the first of the following expressions which has a + /// non- value: , , or ModelType.Name. + /// + /// The display name. + public string GetDisplayName() + { + return DisplayName ?? Name ?? ModelType.Name; + } + + /// + public bool Equals(ModelMetadata other) + { + if (object.ReferenceEquals(this, other)) + { + return true; + } + + if (other == null) + { + return false; + } + else + { + return Identity.Equals(other.Identity); + } + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as ModelMetadata); + } + + /// + public override int GetHashCode() + { + // Normally caching the hashcode would be dangerous, but Identity is deeply immutable so this is safe. + if (_hashCode == null) + { + _hashCode = Identity.GetHashCode(); + } + + return _hashCode.Value; + } + + private void InitializeTypeInformation() + { + Debug.Assert(ModelType != null); + + IsComplexType = !TypeDescriptor.GetConverter(ModelType).CanConvertFrom(typeof(string)); + IsNullableValueType = Nullable.GetUnderlyingType(ModelType) != null; + IsReferenceOrNullableType = !ModelType.GetTypeInfo().IsValueType || IsNullableValueType; + UnderlyingOrModelType = Nullable.GetUnderlyingType(ModelType) ?? ModelType; + + var collectionType = ClosedGenericMatcher.ExtractGenericInterface(ModelType, typeof(ICollection<>)); + IsCollectionType = collectionType != null; + + if (ModelType == typeof(string) || !typeof(IEnumerable).IsAssignableFrom(ModelType)) + { + // Do nothing, not Enumerable. + } + else if (ModelType.IsArray) + { + IsEnumerableType = true; + ElementType = ModelType.GetElementType(); + } + else + { + IsEnumerableType = true; + + var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(ModelType, typeof(IEnumerable<>)); + ElementType = enumerableType?.GenericTypeArguments[0]; + + if (ElementType == null) + { + // ModelType implements IEnumerable but not IEnumerable. + ElementType = typeof(object); + } + + Debug.Assert( + ElementType != null, + $"Unable to find element type for '{ModelType.FullName}' though IsEnumerableType is true."); + } + } + + private string DebuggerToString() + { + switch (MetadataKind) + { + case ModelMetadataKind.Parameter: + return $"ModelMetadata (Parameter: '{ParameterName}' Type: '{ModelType.Name}')"; + case ModelMetadataKind.Property: + return $"ModelMetadata (Property: '{ContainerType.Name}.{PropertyName}' Type: '{ModelType.Name}')"; + case ModelMetadataKind.Type: + return $"ModelMetadata (Type: '{ModelType.Name}')"; + default: + return $"Unsupported MetadataKind '{MetadataKind}'."; + } + } + + /// + public virtual ModelMetadata GetMetadataForType(Type modelType) + { + throw new NotImplementedException(); + } + + /// + public virtual IEnumerable GetMetadataForProperties(Type modelType) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs new file mode 100644 index 0000000000..1b4f01cd6d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.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 System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A provider that can supply instances of . + /// + public abstract class ModelMetadataProvider : IModelMetadataProvider + { + /// + /// Supplies metadata describing the properties of a . + /// + /// The . + /// A set of instances describing properties of the . + public abstract IEnumerable GetMetadataForProperties(Type modelType); + + /// + /// Supplies metadata describing a . + /// + /// The . + /// A instance describing the . + public abstract ModelMetadata GetMetadataForType(Type modelType); + + /// + /// Supplies metadata describing a parameter. + /// + /// The . + /// A instance describing the . + public abstract ModelMetadata GetMetadataForParameter(ParameterInfo parameter); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelPropertyCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelPropertyCollection.cs new file mode 100644 index 0000000000..733da709ba --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelPropertyCollection.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 System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A read-only collection of objects which represent model properties. + /// + public class ModelPropertyCollection : ReadOnlyCollection + { + /// + /// Creates a new . + /// + /// The properties. + public ModelPropertyCollection(IEnumerable properties) + : base(properties.ToList()) + { + } + + /// + /// Gets a instance for the property corresponding to . + /// + /// + /// The property name. Property names are compared using . + /// + /// + /// The instance for the property specified by , or + /// null if no match can be found. + /// + public ModelMetadata this[string propertyName] + { + get + { + if (propertyName == null) + { + throw new ArgumentNullException(nameof(propertyName)); + } + + for (var i = 0; i < Items.Count; i++) + { + var property = Items[i]; + if (string.Equals(property.PropertyName, propertyName, StringComparison.Ordinal)) + { + return property; + } + } + + return null; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs new file mode 100644 index 0000000000..970e5b6975 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs @@ -0,0 +1,1249 @@ +// Copyright (c) .NET 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.Diagnostics; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Represents the state of an attempt to bind values from an HTTP Request to an action method, which includes + /// validation information. + /// + public class ModelStateDictionary : IReadOnlyDictionary + { + // Make sure to update the doc headers if this value is changed. + /// + /// The default value for of 200. + /// + public static readonly int DefaultMaxAllowedErrors = 200; + + private const char DelimiterDot = '.'; + private const char DelimiterOpen = '['; + + private readonly ModelStateNode _root; + private int _maxAllowedErrors; + + /// + /// Initializes a new instance of the class. + /// + public ModelStateDictionary() + : this(DefaultMaxAllowedErrors) + { + } + + /// + /// Initializes a new instance of the class. + /// + public ModelStateDictionary(int maxAllowedErrors) + { + MaxAllowedErrors = maxAllowedErrors; + var emptySegment = new StringSegment(buffer: string.Empty); + _root = new ModelStateNode(subKey: emptySegment) + { + Key = string.Empty + }; + } + + /// + /// Initializes a new instance of the class by using values that are copied + /// from the specified . + /// + /// The to copy values from. + public ModelStateDictionary(ModelStateDictionary dictionary) + : this(dictionary?.MaxAllowedErrors ?? DefaultMaxAllowedErrors) + { + if (dictionary == null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + Merge(dictionary); + } + + /// + /// Root entry for the . + /// + public ModelStateEntry Root => _root; + + /// + /// Gets or sets the maximum allowed model state errors in this instance of . + /// Defaults to 200. + /// + /// + /// + /// tracks the number of model errors added by calls to + /// or + /// . + /// Once the value of MaxAllowedErrors - 1 is reached, if another attempt is made to add an error, + /// the error message will be ignored and a will be added. + /// + /// + /// Errors added via modifying directly do not count towards this limit. + /// + /// + public int MaxAllowedErrors + { + get + { + return _maxAllowedErrors; + } + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _maxAllowedErrors = value; + } + } + + /// + /// Gets a value indicating whether or not the maximum number of errors have been + /// recorded. + /// + /// + /// Returns true if a has been recorded; + /// otherwise false. + /// + public bool HasReachedMaxErrors => ErrorCount >= MaxAllowedErrors; + + /// + /// Gets the number of errors added to this instance of via + /// or . + /// + public int ErrorCount { get; private set; } + + /// + public int Count { get; private set; } + + /// + /// Gets the key sequence. + /// + public KeyEnumerable Keys => new KeyEnumerable(this); + + /// + IEnumerable IReadOnlyDictionary.Keys => Keys; + + /// + /// Gets the value sequence. + /// + public ValueEnumerable Values => new ValueEnumerable(this); + + /// + IEnumerable IReadOnlyDictionary.Values => Values; + + /// + /// Gets a value that indicates whether any model state values in this model state dictionary is invalid or not validated. + /// + public bool IsValid + { + get + { + return ValidationState == ModelValidationState.Valid || ValidationState == ModelValidationState.Skipped; + } + } + + /// + public ModelValidationState ValidationState => GetValidity(_root) ?? ModelValidationState.Valid; + + /// + public ModelStateEntry this[string key] + { + get + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + TryGetValue(key, out var entry); + return entry; + } + } + + // Flag that indicates if TooManyModelErrorException has already been added to this dictionary. + private bool HasRecordedMaxModelError { get; set; } + + /// + /// Adds the specified to the instance + /// that is associated with the specified . If the maximum number of allowed + /// errors has already been recorded, ensures that a exception is + /// recorded instead. + /// + /// + /// This method allows adding the to the current + /// when is not available or the exact + /// must be maintained for later use (even if it is for example a ). + /// Where is available, use instead. + /// + /// The key of the to add errors to. + /// The to add. + /// + /// True if the given error was added, false if the error was ignored. + /// See . + /// + public bool TryAddModelException(string key, Exception exception) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + if (ErrorCount >= MaxAllowedErrors - 1) + { + EnsureMaxErrorsReachedRecorded(); + return false; + } + + ErrorCount++; + AddModelErrorCore(key, exception); + return true; + } + + /// + /// Adds the specified to the instance + /// that is associated with the specified . If the maximum number of allowed + /// errors has already been recorded, ensures that a exception is + /// recorded instead. + /// + /// The key of the to add errors to. + /// The to add. Some exception types will be replaced with + /// a descriptive error message. + /// The associated with the model. + public void AddModelError(string key, Exception exception, ModelMetadata metadata) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + if (metadata == null) + { + throw new ArgumentNullException(nameof(metadata)); + } + + TryAddModelError(key, exception, metadata); + } + + /// + /// Attempts to add the specified to the + /// instance that is associated with the specified . If the maximum number of allowed + /// errors has already been recorded, ensures that a exception is + /// recorded instead. + /// + /// The key of the to add errors to. + /// The to add. Some exception types will be replaced with + /// a descriptive error message. + /// The associated with the model. + /// + /// True if the given error was added, false if the error was ignored. + /// See . + /// + public bool TryAddModelError(string key, Exception exception, ModelMetadata metadata) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (exception == null) + { + throw new ArgumentNullException(nameof(exception)); + } + + if (metadata == null) + { + throw new ArgumentNullException(nameof(metadata)); + } + + if (ErrorCount >= MaxAllowedErrors - 1) + { + EnsureMaxErrorsReachedRecorded(); + return false; + } + + if (exception is FormatException || exception is OverflowException) + { + // Convert FormatExceptions and OverflowExceptions to Invalid value messages. + TryGetValue(key, out var entry); + + // Not using metadata.GetDisplayName() or a single resource to avoid strange messages like + // "The value '' is not valid." (when no value was provided, not even an empty string) and + // "The supplied value is invalid for Int32." (when error is for an element or parameter). + var messageProvider = metadata.ModelBindingMessageProvider; + var name = metadata.DisplayName ?? metadata.PropertyName; + string errorMessage; + if (entry == null && name == null) + { + errorMessage = messageProvider.NonPropertyUnknownValueIsInvalidAccessor(); + } + else if (entry == null) + { + errorMessage = messageProvider.UnknownValueIsInvalidAccessor(name); + } + else if (name == null) + { + errorMessage = messageProvider.NonPropertyAttemptedValueIsInvalidAccessor(entry.AttemptedValue); + } + else + { + errorMessage = messageProvider.AttemptedValueIsInvalidAccessor(entry.AttemptedValue, name); + } + + return TryAddModelError(key, errorMessage); + } + else if (exception is InputFormatterException && !string.IsNullOrEmpty(exception.Message)) + { + // InputFormatterException is a signal that the message is safe to expose to clients + return TryAddModelError(key, exception.Message); + } + + ErrorCount++; + AddModelErrorCore(key, exception); + return true; + } + + /// + /// Adds the specified to the instance + /// that is associated with the specified . If the maximum number of allowed + /// errors has already been recorded, ensures that a exception is + /// recorded instead. + /// + /// The key of the to add errors to. + /// The error message to add. + public void AddModelError(string key, string errorMessage) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (errorMessage == null) + { + throw new ArgumentNullException(nameof(errorMessage)); + } + + TryAddModelError(key, errorMessage); + } + + /// + /// Attempts to add the specified to the + /// instance that is associated with the specified . If the maximum number of allowed + /// errors has already been recorded, ensures that a exception is + /// recorded instead. + /// + /// The key of the to add errors to. + /// The error message to add. + /// + /// True if the given error was added, false if the error was ignored. + /// See . + /// + public bool TryAddModelError(string key, string errorMessage) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (errorMessage == null) + { + throw new ArgumentNullException(nameof(errorMessage)); + } + + if (ErrorCount >= MaxAllowedErrors - 1) + { + EnsureMaxErrorsReachedRecorded(); + return false; + } + + ErrorCount++; + var modelState = GetOrAddNode(key); + Count += !modelState.IsContainerNode ? 0 : 1; + modelState.ValidationState = ModelValidationState.Invalid; + modelState.MarkNonContainerNode(); + modelState.Errors.Add(errorMessage); + + return true; + } + + /// + /// Returns the aggregate for items starting with the + /// specified . + /// + /// The key to look up model state errors for. + /// Returns if no entries are found for the specified + /// key, if at least one instance is found with one or more model + /// state errors; otherwise. + public ModelValidationState GetFieldValidationState(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + var item = GetNode(key); + return GetValidity(item) ?? ModelValidationState.Unvalidated; + } + + /// + /// Returns for the . + /// + /// The key to look up model state errors for. + /// Returns if no entry is found for the specified + /// key, if an instance is found with one or more model + /// state errors; otherwise. + public ModelValidationState GetValidationState(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (TryGetValue(key, out var validationState)) + { + return validationState.ValidationState; + } + + return ModelValidationState.Unvalidated; + } + + /// + /// Marks the for the entry with the specified + /// as . + /// + /// The key of the to mark as valid. + public void MarkFieldValid(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + var modelState = GetOrAddNode(key); + if (modelState.ValidationState == ModelValidationState.Invalid) + { + throw new InvalidOperationException(Resources.Validation_InvalidFieldCannotBeReset); + } + + Count += !modelState.IsContainerNode ? 0 : 1; + modelState.MarkNonContainerNode(); + modelState.ValidationState = ModelValidationState.Valid; + } + + /// + /// Marks the for the entry with the specified + /// as . + /// + /// The key of the to mark as skipped. + public void MarkFieldSkipped(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + var modelState = GetOrAddNode(key); + if (modelState.ValidationState == ModelValidationState.Invalid) + { + throw new InvalidOperationException(Resources.Validation_InvalidFieldCannotBeReset_ToSkipped); + } + + Count += !modelState.IsContainerNode ? 0 : 1; + modelState.MarkNonContainerNode(); + modelState.ValidationState = ModelValidationState.Skipped; + } + + /// + /// Copies the values from the specified into this instance, overwriting + /// existing values if keys are the same. + /// + /// The to copy values from. + public void Merge(ModelStateDictionary dictionary) + { + if (dictionary == null) + { + return; + } + + foreach (var source in dictionary) + { + var target = GetOrAddNode(source.Key); + Count += !target.IsContainerNode ? 0 : 1; + ErrorCount += source.Value.Errors.Count - target.Errors.Count; + target.Copy(source.Value); + target.MarkNonContainerNode(); + } + } + + /// + /// Sets the of and for + /// the with the specified . + /// + /// The key for the entry. + /// The raw value for the entry. + /// + /// The values of in a comma-separated . + /// + public void SetModelValue(string key, object rawValue, string attemptedValue) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + var modelState = GetOrAddNode(key); + Count += !modelState.IsContainerNode ? 0 : 1; + modelState.RawValue = rawValue; + modelState.AttemptedValue = attemptedValue; + modelState.MarkNonContainerNode(); + } + + /// + /// Sets the value for the with the specified . + /// + /// The key for the entry + /// + /// A with data for the entry. + /// + public void SetModelValue(string key, ValueProviderResult valueProviderResult) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + // Avoid creating a new array for rawValue if there's only one value. + object rawValue; + if (valueProviderResult == ValueProviderResult.None) + { + rawValue = null; + } + else if (valueProviderResult.Length == 1) + { + rawValue = valueProviderResult.Values[0]; + } + else + { + rawValue = valueProviderResult.Values.ToArray(); + } + + SetModelValue(key, rawValue, valueProviderResult.ToString()); + } + + /// + /// Clears entries that match the key that is passed as parameter. + /// + /// The key of to clear. + public void ClearValidationState(string key) + { + // If key is null or empty, clear all entries in the dictionary + // else just clear the ones that have key as prefix + var entries = FindKeysWithPrefix(key ?? string.Empty); + foreach (var entry in entries) + { + entry.Value.Errors.Clear(); + entry.Value.ValidationState = ModelValidationState.Unvalidated; + } + } + + private ModelStateNode GetNode(string key) + { + Debug.Assert(key != null); + + var current = _root; + if (key.Length > 0) + { + var match = default(MatchResult); + do + { + var subKey = FindNext(key, ref match); + current = current.GetNode(subKey); + + // Path not found, exit early + if (current == null) + { + break; + } + + } while (match.Type != Delimiter.None); + } + + return current; + } + + private ModelStateNode GetOrAddNode(string key) + { + Debug.Assert(key != null); + // For a key of the format, foo.bar[0].baz[qux] we'll create the following nodes: + // foo + // -> bar + // -> [0] + // -> baz + // -> [qux] + + var current = _root; + if (key.Length > 0) + { + var match = default(MatchResult); + do + { + var subKey = FindNext(key, ref match); + current = current.GetOrAddNode(subKey); + + } while (match.Type != Delimiter.None); + + if (current.Key == null) + { + // New Node - Set key + current.Key = key; + } + } + + return current; + } + + // Shared function factored out for clarity, force inlining to put back in + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static StringSegment FindNext(string key, ref MatchResult currentMatch) + { + var index = currentMatch.Index; + var matchType = Delimiter.None; + + for (; index < key.Length; index++) + { + var ch = key[index]; + if (ch == DelimiterDot) + { + matchType = Delimiter.Dot; + break; + } + else if (ch == DelimiterOpen) + { + matchType = Delimiter.OpenBracket; + break; + } + } + + var keyStart = currentMatch.Type == Delimiter.OpenBracket + ? currentMatch.Index - 1 + : currentMatch.Index; + + currentMatch.Type = matchType; + currentMatch.Index = index + 1; + + return new StringSegment(key, keyStart, index - keyStart); + } + + private static ModelValidationState? GetValidity(ModelStateNode node) + { + if (node == null) + { + return null; + } + + ModelValidationState? validationState = null; + if (!node.IsContainerNode) + { + validationState = ModelValidationState.Valid; + if (node.ValidationState == ModelValidationState.Unvalidated) + { + // If any entries of a field is unvalidated, we'll treat the tree as unvalidated. + return ModelValidationState.Unvalidated; + } + + if (node.ValidationState == ModelValidationState.Invalid) + { + validationState = node.ValidationState; + } + } + + if (node.ChildNodes != null) + { + for (var i = 0; i < node.ChildNodes.Count; i++) + { + var entryState = GetValidity(node.ChildNodes[i]); + + if (entryState == ModelValidationState.Unvalidated) + { + return entryState; + } + + if (validationState == null || entryState == ModelValidationState.Invalid) + { + validationState = entryState; + } + } + } + + return validationState; + } + + private void EnsureMaxErrorsReachedRecorded() + { + if (!HasRecordedMaxModelError) + { + var exception = new TooManyModelErrorsException(Resources.ModelStateDictionary_MaxModelStateErrors); + AddModelErrorCore(string.Empty, exception); + HasRecordedMaxModelError = true; + ErrorCount++; + } + } + + private void AddModelErrorCore(string key, Exception exception) + { + var modelState = GetOrAddNode(key); + Count += !modelState.IsContainerNode ? 0 : 1; + modelState.ValidationState = ModelValidationState.Invalid; + modelState.MarkNonContainerNode(); + modelState.Errors.Add(exception); + } + + /// + /// Removes all keys and values from this instance of . + /// + public void Clear() + { + Count = 0; + HasRecordedMaxModelError = false; + ErrorCount = 0; + _root.Reset(); + _root.ChildNodes?.Clear(); + } + + /// + public bool ContainsKey(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + return !GetNode(key)?.IsContainerNode ?? false; + } + + /// + /// Removes the with the specified . + /// + /// The key. + /// true if the element is successfully removed; otherwise false. This method also + /// returns false if key was not found. + public bool Remove(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + var node = GetNode(key); + if (node?.IsContainerNode == false) + { + Count--; + ErrorCount -= node.Errors.Count; + node.Reset(); + return true; + } + + return false; + } + + /// + public bool TryGetValue(string key, out ModelStateEntry value) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + var result = GetNode(key); + if (result?.IsContainerNode == false) + { + value = result; + return true; + } + + value = null; + return false; + } + + /// + /// Returns an enumerator that iterates through this instance of . + /// + /// An . + public Enumerator GetEnumerator() => new Enumerator(this, prefix: string.Empty); + + /// + IEnumerator> + IEnumerable>.GetEnumerator() => GetEnumerator(); + + /// + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + public static bool StartsWithPrefix(string prefix, string key) + { + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (prefix.Length == 0) + { + // Everything is prefixed by the empty string. + return true; + } + + if (prefix.Length > key.Length) + { + return false; // Not long enough. + } + + if (!key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + return false; + } + + if (key.Length == prefix.Length) + { + // Exact match + return true; + } + + var charAfterPrefix = key[prefix.Length]; + if (charAfterPrefix == '.' || charAfterPrefix == '[') + { + return true; + } + + return false; + } + + public PrefixEnumerable FindKeysWithPrefix(string prefix) + { + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + return new PrefixEnumerable(this, prefix); + } + + private struct MatchResult + { + public Delimiter Type; + public int Index; + } + + private enum Delimiter + { + None = 0, + Dot, + OpenBracket + } + + [DebuggerDisplay("SubKey={SubKey}, Key={Key}, ValidationState={ValidationState}")] + private class ModelStateNode : ModelStateEntry + { + private bool _isContainerNode = true; + + public ModelStateNode(StringSegment subKey) + { + SubKey = subKey; + } + + public List ChildNodes { get; set; } + + public override IReadOnlyList Children => ChildNodes; + + public string Key { get; set; } + + public StringSegment SubKey { get; } + + public override bool IsContainerNode => _isContainerNode; + + public void MarkNonContainerNode() + { + _isContainerNode = false; + } + + public void Copy(ModelStateEntry entry) + { + RawValue = entry.RawValue; + AttemptedValue = entry.AttemptedValue; + Errors.Clear(); + for (var i = 0; i < entry.Errors.Count; i++) + { + Errors.Add(entry.Errors[i]); + } + + ValidationState = entry.ValidationState; + } + + public void Reset() + { + _isContainerNode = true; + RawValue = null; + AttemptedValue = null; + ValidationState = ModelValidationState.Unvalidated; + Errors.Clear(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ModelStateNode GetNode(StringSegment subKey) + { + ModelStateNode modelStateNode = null; + if (subKey.Length == 0) + { + modelStateNode = this; + } + else if (ChildNodes != null) + { + var index = BinarySearch(subKey); + if (index >= 0) + { + modelStateNode = ChildNodes[index]; + } + } + + return modelStateNode; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ModelStateNode GetOrAddNode(StringSegment subKey) + { + ModelStateNode modelStateNode; + if (subKey.Length == 0) + { + modelStateNode = this; + } + else if (ChildNodes == null) + { + ChildNodes = new List(1); + modelStateNode = new ModelStateNode(subKey); + ChildNodes.Add(modelStateNode); + } + else + { + var index = BinarySearch(subKey); + if (index >= 0) + { + modelStateNode = ChildNodes[index]; + } + else + { + modelStateNode = new ModelStateNode(subKey); + ChildNodes.Insert(~index, modelStateNode); + } + } + + return modelStateNode; + } + + public override ModelStateEntry GetModelStateForProperty(string propertyName) + => GetNode(new StringSegment(propertyName)); + + private int BinarySearch(StringSegment searchKey) + { + Debug.Assert(ChildNodes != null); + + var low = 0; + var high = ChildNodes.Count - 1; + while (low <= high) + { + var mid = low + ((high - low) / 2); + var midKey = ChildNodes[mid].SubKey; + var result = midKey.Length - searchKey.Length; + if (result == 0) + { + result = string.Compare( + midKey.Buffer, + midKey.Offset, + searchKey.Buffer, + searchKey.Offset, + searchKey.Length, + StringComparison.OrdinalIgnoreCase); + } + + if (result == 0) + { + return mid; + } + if (result < 0) + { + low = mid + 1; + } + else + { + high = mid - 1; + } + } + + return ~low; + } + } + + public struct PrefixEnumerable : IEnumerable> + { + private readonly ModelStateDictionary _dictionary; + private readonly string _prefix; + + public PrefixEnumerable(ModelStateDictionary dictionary, string prefix) + { + if (dictionary == null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + _dictionary = dictionary; + _prefix = prefix; + } + + public Enumerator GetEnumerator() => new Enumerator(_dictionary, _prefix); + + IEnumerator> + IEnumerable>.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public struct Enumerator : IEnumerator> + { + private readonly ModelStateNode _rootNode; + private ModelStateNode _modelStateNode; + private List _nodes; + private int _index; + private bool _visitedRoot; + + public Enumerator(ModelStateDictionary dictionary, string prefix) + { + if (dictionary == null) + { + throw new ArgumentNullException(nameof(dictionary)); + } + + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + _index = -1; + _rootNode = dictionary.GetNode(prefix); + _modelStateNode = null; + _nodes = null; + _visitedRoot = false; + } + + public KeyValuePair Current => + new KeyValuePair(_modelStateNode.Key, _modelStateNode); + + object IEnumerator.Current => Current; + + public void Dispose() + { + } + + public bool MoveNext() + { + if (_rootNode == null) + { + return false; + } + + if (!_visitedRoot) + { + // Visit the root node + _visitedRoot = true; + if (_rootNode.ChildNodes?.Count > 0) + { + _nodes = new List { _rootNode }; + } + + if (!_rootNode.IsContainerNode) + { + _modelStateNode = _rootNode; + return true; + } + } + + if (_nodes == null) + { + return false; + } + + while (_nodes.Count > 0) + { + var node = _nodes[0]; + if (_index == node.ChildNodes.Count - 1) + { + // We've exhausted the current sublist. + _nodes.RemoveAt(0); + _index = -1; + continue; + } + else + { + _index++; + } + + var currentChild = node.ChildNodes[_index]; + if (currentChild.ChildNodes?.Count > 0) + { + _nodes.Add(currentChild); + } + + if (!currentChild.IsContainerNode) + { + _modelStateNode = currentChild; + return true; + } + } + + return false; + } + + public void Reset() + { + _index = -1; + _nodes.Clear(); + _visitedRoot = false; + _modelStateNode = null; + } + } + + public struct KeyEnumerable : IEnumerable + { + private readonly ModelStateDictionary _dictionary; + + public KeyEnumerable(ModelStateDictionary dictionary) + { + _dictionary = dictionary; + } + + public KeyEnumerator GetEnumerator() => new KeyEnumerator(_dictionary, prefix: string.Empty); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public struct KeyEnumerator : IEnumerator + { + private Enumerator _prefixEnumerator; + + public KeyEnumerator(ModelStateDictionary dictionary, string prefix) + { + _prefixEnumerator = new Enumerator(dictionary, prefix); + Current = null; + } + + public string Current { get; private set; } + + object IEnumerator.Current => Current; + + public void Dispose() => _prefixEnumerator.Dispose(); + + public bool MoveNext() + { + var result = _prefixEnumerator.MoveNext(); + if (result) + { + var current = _prefixEnumerator.Current; + Current = current.Key; + } + else + { + Current = null; + } + + return result; + } + + public void Reset() + { + _prefixEnumerator.Reset(); + Current = null; + } + } + + public struct ValueEnumerable : IEnumerable + { + private readonly ModelStateDictionary _dictionary; + + public ValueEnumerable(ModelStateDictionary dictionary) + { + _dictionary = dictionary; + } + + public ValueEnumerator GetEnumerator() => new ValueEnumerator(_dictionary, prefix: string.Empty); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public struct ValueEnumerator : IEnumerator + { + private Enumerator _prefixEnumerator; + + public ValueEnumerator(ModelStateDictionary dictionary, string prefix) + { + _prefixEnumerator = new Enumerator(dictionary, prefix); + Current = null; + } + + public ModelStateEntry Current { get; private set; } + + object IEnumerator.Current => Current; + + public void Dispose() => _prefixEnumerator.Dispose(); + + public bool MoveNext() + { + var result = _prefixEnumerator.MoveNext(); + if (result) + { + var current = _prefixEnumerator.Current; + Current = current.Value; + } + else + { + Current = null; + } + + return result; + } + + public void Reset() + { + _prefixEnumerator.Reset(); + Current = null; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateEntry.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateEntry.cs new file mode 100644 index 0000000000..b73eab4f77 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateEntry.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An entry in a . + /// + public abstract class ModelStateEntry + { + private ModelErrorCollection _errors; + + /// + /// Gets the raw value from the request associated with this entry. + /// + public object RawValue { get; set; } + + /// + /// Gets the set of values contained in , joined into a comma-separated string. + /// + public string AttemptedValue { get; set; } + + /// + /// Gets the for this entry. + /// + public ModelErrorCollection Errors + { + get + { + if (_errors == null) + { + _errors = new ModelErrorCollection(); + } + return _errors; + } + } + + /// + /// Gets or sets the for this entry. + /// + public ModelValidationState ValidationState { get; set; } + + /// + /// Gets a value that determines if the current instance of is a container node. + /// Container nodes represent prefix nodes that aren't explicitly added to the + /// . + /// + public abstract bool IsContainerNode { get; } + + /// + /// Gets the for a sub-property with the specified + /// . + /// + /// The property name to lookup. + /// + /// The if a sub-property was found; otherwise . + /// + /// + /// This method returns any existing entry, even those with with value + /// . + /// + public abstract ModelStateEntry GetModelStateForProperty(string propertyName); + + /// + /// Gets the values for sub-properties. + /// + /// + /// This property returns all existing entries, even those with with value + /// . + /// + public abstract IReadOnlyList Children { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelValidationState.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelValidationState.cs new file mode 100644 index 0000000000..05cc0ee317 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelValidationState.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.ModelBinding +{ + public enum ModelValidationState + { + Unvalidated, + Invalid, + Valid, + Skipped + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/TooManyModelErrorsException.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/TooManyModelErrorsException.cs new file mode 100644 index 0000000000..58f3cdbe42 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/TooManyModelErrorsException.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.ModelBinding +{ + /// + /// The that is thrown when too many model errors are encountered. + /// + public class TooManyModelErrorsException : Exception + { + /// + /// Creates a new instance of with the specified + /// exception . + /// + /// The message that describes the error. + public TooManyModelErrorsException(string message) + : base(message) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientModelValidationContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientModelValidationContext.cs new file mode 100644 index 0000000000..7d04881119 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientModelValidationContext.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// The context for client-side model validation. + /// + public class ClientModelValidationContext : ModelValidationContextBase + { + /// + /// Create a new instance of . + /// + /// The for validation. + /// The for validation. + /// The to be used in validation. + /// The attributes dictionary for the HTML tag being rendered. + public ClientModelValidationContext( + ActionContext actionContext, + ModelMetadata metadata, + IModelMetadataProvider metadataProvider, + IDictionary attributes) + : base(actionContext, metadata, metadataProvider) + { + Attributes = attributes; + } + + /// + /// Gets the attributes dictionary for the HTML tag being rendered. + /// + public IDictionary Attributes { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorItem.cs new file mode 100644 index 0000000000..e346509701 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorItem.cs @@ -0,0 +1,45 @@ +// Copyright (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.ModelBinding.Validation +{ + /// + /// Used to associate validators with instances + /// as part of . An should + /// inspect and set and + /// as appropriate. + /// + public class ClientValidatorItem + { + /// + /// Creates a new . + /// + public ClientValidatorItem() + { + } + + /// + /// Creates a new . + /// + /// The . + public ClientValidatorItem(object validatorMetadata) + { + ValidatorMetadata = validatorMetadata; + } + + /// + /// Gets the metadata associated with the . + /// + public object ValidatorMetadata { get; } + + /// + /// Gets or sets the . + /// + public IClientModelValidator Validator { get; set; } + + /// + /// Gets or sets a value indicating whether or not can be reused across requests. + /// + public bool IsReusable { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorProviderContext.cs new file mode 100644 index 0000000000..84b02119b3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorProviderContext.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// A context for . + /// + public class ClientValidatorProviderContext + { + /// + /// Creates a new . + /// + /// The for the model being validated. + /// + /// The list of s. + public ClientValidatorProviderContext(ModelMetadata modelMetadata, IList items) + { + ModelMetadata = modelMetadata; + Results = items; + } + + /// + /// Gets the . + /// + public ModelMetadata ModelMetadata { get; } + + /// + /// Gets the validator metadata. + /// + /// + /// This property provides convenience access to . + /// + public IReadOnlyList ValidatorMetadata => ModelMetadata.ValidatorMetadata; + + /// + /// Gets the list of instances. + /// instances should add the appropriate properties when + /// + /// is called. + /// + public IList Results { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidator.cs new file mode 100644 index 0000000000..6a46cec649 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidator.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 Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + public interface IClientModelValidator + { + void AddValidation(ClientModelValidationContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidatorProvider.cs new file mode 100644 index 0000000000..adf01cf8f8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidatorProvider.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.ModelBinding.Validation +{ + /// + /// Provides a collection of s. + /// + public interface IClientModelValidatorProvider + { + /// + /// Creates set of s by updating + /// in . + /// + /// The associated with this call. + void CreateValidators(ClientValidatorProviderContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidator.cs new file mode 100644 index 0000000000..f2f0ae34ef --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidator.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// Validates a model value. + /// + public interface IModelValidator + { + /// + /// Validates the model value. + /// + /// The . + /// + /// A list of indicating the results of validating the model value. + /// + IEnumerable Validate(ModelValidationContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidatorProvider.cs new file mode 100644 index 0000000000..9ed1704ca9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidatorProvider.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.ModelBinding.Validation +{ + /// + /// Provides validators for a model value. + /// + public interface IModelValidatorProvider + { + /// + /// Creates the validators for . + /// + /// The . + /// + /// Implementations should add the instances to the appropriate + /// instance which should be added to + /// . + /// + void CreateValidators(ModelValidatorProviderContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IPropertyValidationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IPropertyValidationFilter.cs new file mode 100644 index 0000000000..622b41f70c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IPropertyValidationFilter.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.ModelBinding.Validation +{ + /// + /// Contract for attributes that determine whether associated properties should be validated. When the attribute is + /// applied to a property, the validation system calls to determine whether to + /// validate that property. When applied to a type, the validation system calls + /// for each property that type defines to determine whether to validate it. + /// + public interface IPropertyValidationFilter + { + /// + /// Gets an indication whether the should be validated. + /// + /// to check. + /// containing . + /// true if should be validated; false otherwise. + bool ShouldValidateEntry(ValidationEntry entry, ValidationEntry parentEntry); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IValidationStrategy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IValidationStrategy.cs new file mode 100644 index 0000000000..4419957230 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IValidationStrategy.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// Defines a strategy for enumerating the child entries of a model object which should be validated. + /// + public interface IValidationStrategy + { + /// + /// Gets an containing a for + /// each child entry of the model object to be validated. + /// + /// The associated with . + /// The model prefix associated with . + /// The model object. + /// An . + IEnumerator GetChildren(ModelMetadata metadata, string key, object model); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContext.cs new file mode 100644 index 0000000000..78f48f32fb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContext.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// A context object for . + /// + public class ModelValidationContext : ModelValidationContextBase + { + /// + /// Create a new instance of . + /// + /// The for validation. + /// The for validation. + /// The to be used in validation. + /// The model container. + /// The model to be validated. + public ModelValidationContext( + ActionContext actionContext, + ModelMetadata modelMetadata, + IModelMetadataProvider metadataProvider, + object container, + object model) + : base(actionContext, modelMetadata, metadataProvider) + { + Container = container; + Model = model; + } + + /// + /// Gets the model object. + /// + public object Model { get; } + + /// + /// Gets the model container object. + /// + public object Container { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContextBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContextBase.cs new file mode 100644 index 0000000000..ea341ef62e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContextBase.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET 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.ModelBinding.Validation +{ + /// + /// A common base class for and . + /// + public class ModelValidationContextBase + { + /// + /// Instantiates a new . + /// + /// The for this context. + /// The for this model. + /// The to be used by this context. + public ModelValidationContextBase( + ActionContext actionContext, + ModelMetadata modelMetadata, + IModelMetadataProvider metadataProvider) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + if (modelMetadata == null) + { + throw new ArgumentNullException(nameof(modelMetadata)); + } + + if (metadataProvider == null) + { + throw new ArgumentNullException(nameof(metadataProvider)); + } + + ActionContext = actionContext; + ModelMetadata = modelMetadata; + MetadataProvider = metadataProvider; + } + + /// + /// Gets the . + /// + public ActionContext ActionContext { get; } + + /// + /// Gets the . + /// + public ModelMetadata ModelMetadata { get; } + + /// + /// Gets the . + /// + public IModelMetadataProvider MetadataProvider { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationResult.cs new file mode 100644 index 0000000000..0d00621ed0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationResult.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.ModelBinding.Validation +{ + public class ModelValidationResult + { + public ModelValidationResult(string memberName, string message) + { + MemberName = memberName ?? string.Empty; + Message = message ?? string.Empty; + } + + public string MemberName { get; } + + public string Message { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidatorProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidatorProviderContext.cs new file mode 100644 index 0000000000..8076c82da4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidatorProviderContext.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET 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.ModelBinding.Validation +{ + /// + /// A context for . + /// + public class ModelValidatorProviderContext + { + /// + /// Creates a new . + /// + /// The . + /// The list of s. + public ModelValidatorProviderContext(ModelMetadata modelMetadata, IList items) + { + ModelMetadata = modelMetadata; + Results = items; + } + + /// + /// Gets the . + /// + public ModelMetadata ModelMetadata { get; set; } + + /// + /// Gets the validator metadata. + /// + /// + /// This property provides convenience access to . + /// + public IReadOnlyList ValidatorMetadata => ModelMetadata.ValidatorMetadata; + + /// + /// Gets the list of instances. instances + /// should add the appropriate properties when + /// + /// is called. + /// + public IList Results { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationEntry.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationEntry.cs new file mode 100644 index 0000000000..a873273ca7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationEntry.cs @@ -0,0 +1,96 @@ +// Copyright (c) .NET 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.ModelBinding.Validation +{ + /// + /// Contains data needed for validating a child entry of a model object. See . + /// + public struct ValidationEntry + { + private object _model; + private Func _modelAccessor; + + /// + /// Creates a new . + /// + /// The associated with . + /// The model prefix associated with . + /// The model object. + public ValidationEntry(ModelMetadata metadata, string key, object model) + { + if (metadata == null) + { + throw new ArgumentNullException(nameof(metadata)); + } + + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + Metadata = metadata; + Key = key; + _model = model; + _modelAccessor = null; + } + + /// + /// Creates a new . + /// + /// The associated with the . + /// The model prefix associated with the . + /// A delegate that will return the . + public ValidationEntry(ModelMetadata metadata, string key, Func modelAccessor) + { + if (metadata == null) + { + throw new ArgumentNullException(nameof(metadata)); + } + + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (modelAccessor == null) + { + throw new ArgumentNullException(nameof(modelAccessor)); + } + + Metadata = metadata; + Key = key; + _model = null; + _modelAccessor = modelAccessor; + } + + /// + /// The model prefix associated with . + /// + public string Key { get; } + + /// + /// The associated with . + /// + public ModelMetadata Metadata { get; } + + /// + /// The model object. + /// + public object Model + { + get + { + if (_modelAccessor != null) + { + _model = _modelAccessor(); + _modelAccessor = null; + } + + return _model; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateDictionary.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateDictionary.cs new file mode 100644 index 0000000000..11179b014d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateDictionary.cs @@ -0,0 +1,154 @@ +// Copyright (c) .NET 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.Runtime.CompilerServices; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// Used for tracking validation state to customize validation behavior for a model object. + /// + public class ValidationStateDictionary : + IDictionary, + IReadOnlyDictionary + { + private readonly Dictionary _inner; + + /// + /// Creates a new . + /// + public ValidationStateDictionary() + { + _inner = new Dictionary(ReferenceEqualityComparer.Instance); + } + + /// + public ValidationStateEntry this[object key] + { + get + { + TryGetValue(key, out var entry); + return entry; + } + + set + { + _inner[key] = value; + } + } + + /// + public int Count => _inner.Count; + + /// + public bool IsReadOnly => ((IDictionary)_inner).IsReadOnly; + + /// + public ICollection Keys => ((IDictionary)_inner).Keys; + + /// + public ICollection Values => ((IDictionary)_inner).Values; + + /// + IEnumerable IReadOnlyDictionary.Keys => + ((IReadOnlyDictionary)_inner).Keys; + + /// + IEnumerable IReadOnlyDictionary.Values => + ((IReadOnlyDictionary)_inner).Values; + + /// + public void Add(KeyValuePair item) + { + ((IDictionary)_inner).Add(item); + } + + /// + public void Add(object key, ValidationStateEntry value) + { + _inner.Add(key, value); + } + + /// + public void Clear() + { + _inner.Clear(); + } + + /// + public bool Contains(KeyValuePair item) + { + return ((IDictionary)_inner).Contains(item); + } + + /// + public bool ContainsKey(object key) + { + return _inner.ContainsKey(key); + } + + /// + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + ((IDictionary)_inner).CopyTo(array, arrayIndex); + } + + /// + public IEnumerator> GetEnumerator() + { + return ((IDictionary)_inner).GetEnumerator(); + } + + /// + public bool Remove(KeyValuePair item) + { + return _inner.Remove(item); + } + + /// + public bool Remove(object key) + { + return _inner.Remove(key); + } + + /// + public bool TryGetValue(object key, out ValidationStateEntry value) + { + return _inner.TryGetValue(key, out value); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return ((IDictionary)_inner).GetEnumerator(); + } + + private class ReferenceEqualityComparer : IEqualityComparer + { + private static readonly bool IsMono = Type.GetType("Mono.Runtime") != null; + + public static readonly ReferenceEqualityComparer Instance = new ReferenceEqualityComparer(); + + public new bool Equals(object x, object y) + { + return Object.ReferenceEquals(x, y); + } + + public int GetHashCode(object obj) + { + // RuntimeHelpers.GetHashCode sometimes crashes the runtime on Mono 4.0.4 + // See: https://github.com/aspnet/External/issues/45 + // The workaround here is to just not hash anything, and fall back to an equality check. + if (IsMono) + { + return 0; + } + + return RuntimeHelpers.GetHashCode(obj); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateEntry.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateEntry.cs new file mode 100644 index 0000000000..970cbb8dfa --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateEntry.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// An entry in a . Records state information to override the default + /// behavior of validation for an object. + /// + public class ValidationStateEntry + { + /// + /// Gets or sets the model prefix associated with the entry. + /// + public string Key { get; set; } + + /// + /// Gets or sets the associated with the entry. + /// + public ModelMetadata Metadata { get; set; } + + /// + /// Gets or sets a value indicating whether the associated model object should be validated. + /// + public bool SuppressValidation { get; set; } + + /// + /// Gets or sets an for enumerating child entries of the associated + /// model object. + /// + public IValidationStrategy Strategy { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidatorItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidatorItem.cs new file mode 100644 index 0000000000..8592eeca71 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidatorItem.cs @@ -0,0 +1,45 @@ +// Copyright (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.ModelBinding.Validation +{ + /// + /// Used to associate validators with instances + /// as part of . An should + /// inspect and set and + /// as appropriate. + /// + public class ValidatorItem + { + /// + /// Creates a new . + /// + public ValidatorItem() + { + } + + /// + /// Creates a new . + /// + /// The . + public ValidatorItem(object validatorMetadata) + { + ValidatorMetadata = validatorMetadata; + } + + /// + /// Gets the metadata associated with the . + /// + public object ValidatorMetadata { get; } + + /// + /// Gets or sets the . + /// + public IModelValidator Validator { get; set; } + + /// + /// Gets or sets a value indicating whether or not can be reused across requests. + /// + public bool IsReusable { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderFactoryContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderFactoryContext.cs new file mode 100644 index 0000000000..cf48d9072d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderFactoryContext.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A context for . + /// + public class ValueProviderFactoryContext + { + /// + /// Creates a new . + /// + /// The . + public ValueProviderFactoryContext(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + ActionContext = context; + } + + /// + /// Gets the associated with this context. + /// + public ActionContext ActionContext { get; } + + /// + /// Gets the list of instances. + /// instances should add the appropriate + /// instances to this list. + /// + public IList ValueProviders { get; } = new List(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderResult.cs new file mode 100644 index 0000000000..c9bf19ad4c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderResult.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.Collections; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Result of an operation. + /// + /// + /// + /// can represent a single submitted value or multiple submitted values. + /// + /// + /// Use to consume only a single value, regardless of whether a single value or + /// multiple values were submitted. + /// + /// + /// Treat as an to consume all values, + /// regardless of whether a single value or multiple values were submitted. + /// + /// + public struct ValueProviderResult : IEquatable, IEnumerable + { + private static readonly CultureInfo _invariantCulture = CultureInfo.InvariantCulture; + + /// + /// A that represents a lack of data. + /// + public static ValueProviderResult None = new ValueProviderResult(new string[0]); + + /// + /// Creates a new using . + /// + /// The submitted values. + public ValueProviderResult(StringValues values) + : this(values, _invariantCulture) + { + } + + /// + /// Creates a new . + /// + /// The submitted values. + /// The associated with this value. + public ValueProviderResult(StringValues values, CultureInfo culture) + { + Values = values; + Culture = culture ?? _invariantCulture; + } + + /// + /// Gets or sets the associated with the values. + /// + public CultureInfo Culture { get; } + + /// + /// Gets or sets the values. + /// + public StringValues Values { get; } + + /// + /// Gets the first value based on the order values were provided in the request. Use + /// to get a single value for processing regardless of whether a single or multiple values were provided + /// in the request. + /// + public string FirstValue + { + get + { + if (Values.Count == 0) + { + return null; + } + return Values[0]; + } + } + + /// + /// Gets the number of submitted values. + /// + public int Length => Values.Count; + + /// + public override bool Equals(object obj) + { + var other = obj as ValueProviderResult?; + return other.HasValue && Equals(other.Value); + } + + /// + public bool Equals(ValueProviderResult other) + { + if (Length != other.Length) + { + return false; + } + else + { + var x = Values; + var y = other.Values; + for (var i = 0; i < x.Count; i++) + { + if (!string.Equals(x[i], y[i], StringComparison.Ordinal)) + { + return false; + } + } + return true; + } + } + + /// + public override int GetHashCode() + { + return ToString().GetHashCode(); + } + + /// + public override string ToString() + { + return Values.ToString(); + } + + /// + /// Gets an for this . + /// + /// An . + public IEnumerator GetEnumerator() + { + return ((IEnumerable)Values).GetEnumerator(); + } + + /// + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + /// + /// Converts the provided into a comma-separated string containing all + /// submitted values. + /// + /// The . + public static explicit operator string(ValueProviderResult result) + { + return result.Values; + } + + /// + /// Converts the provided into a an array of containing + /// all submitted values. + /// + /// The . + public static explicit operator string[](ValueProviderResult result) + { + return result.Values; + } + + /// + /// Compares two objects for equality. + /// + /// A . + /// A . + /// true if the values are equal, otherwise false. + public static bool operator ==(ValueProviderResult x, ValueProviderResult y) + { + return x.Equals(y); + } + + /// + /// Compares two objects for inequality. + /// + /// A . + /// A . + /// false if the values are equal, otherwise true. + public static bool operator !=(ValueProviderResult x, ValueProviderResult y) + { + return !x.Equals(y); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..3af3e8d9b2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/AssemblyInfo.cs @@ -0,0 +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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Abstractions.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..f19e063180 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/Resources.Designer.cs @@ -0,0 +1,296 @@ +// +namespace Microsoft.AspNetCore.Mvc.Abstractions +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Mvc.Abstractions.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// Value cannot be null or empty. + /// + internal static string ArgumentCannotBeNullOrEmpty + { + get => GetString("ArgumentCannotBeNullOrEmpty"); + } + + /// + /// Value cannot be null or empty. + /// + internal static string FormatArgumentCannotBeNullOrEmpty() + => GetString("ArgumentCannotBeNullOrEmpty"); + + /// + /// The ModelMetadata property must be set before accessing this property. + /// + internal static string ModelBindingContext_ModelMetadataMustBeSet + { + get => GetString("ModelBindingContext_ModelMetadataMustBeSet"); + } + + /// + /// The ModelMetadata property must be set before accessing this property. + /// + internal static string FormatModelBindingContext_ModelMetadataMustBeSet() + => GetString("ModelBindingContext_ModelMetadataMustBeSet"); + + /// + /// A field previously marked invalid should not be marked valid. + /// + internal static string Validation_InvalidFieldCannotBeReset + { + get => GetString("Validation_InvalidFieldCannotBeReset"); + } + + /// + /// A field previously marked invalid should not be marked valid. + /// + internal static string FormatValidation_InvalidFieldCannotBeReset() + => GetString("Validation_InvalidFieldCannotBeReset"); + + /// + /// A field previously marked invalid should not be marked skipped. + /// + internal static string Validation_InvalidFieldCannotBeReset_ToSkipped + { + get => GetString("Validation_InvalidFieldCannotBeReset_ToSkipped"); + } + + /// + /// A field previously marked invalid should not be marked skipped. + /// + internal static string FormatValidation_InvalidFieldCannotBeReset_ToSkipped() + => GetString("Validation_InvalidFieldCannotBeReset_ToSkipped"); + + /// + /// The maximum number of allowed model errors has been reached. + /// + internal static string ModelStateDictionary_MaxModelStateErrors + { + get => GetString("ModelStateDictionary_MaxModelStateErrors"); + } + + /// + /// The maximum number of allowed model errors has been reached. + /// + internal static string FormatModelStateDictionary_MaxModelStateErrors() + => GetString("ModelStateDictionary_MaxModelStateErrors"); + + /// + /// Body + /// + internal static string BindingSource_Body + { + get => GetString("BindingSource_Body"); + } + + /// + /// Body + /// + internal static string FormatBindingSource_Body() + => GetString("BindingSource_Body"); + + /// + /// Custom + /// + internal static string BindingSource_Custom + { + get => GetString("BindingSource_Custom"); + } + + /// + /// Custom + /// + internal static string FormatBindingSource_Custom() + => GetString("BindingSource_Custom"); + + /// + /// Form + /// + internal static string BindingSource_Form + { + get => GetString("BindingSource_Form"); + } + + /// + /// Form + /// + internal static string FormatBindingSource_Form() + => GetString("BindingSource_Form"); + + /// + /// Header + /// + internal static string BindingSource_Header + { + get => GetString("BindingSource_Header"); + } + + /// + /// Header + /// + internal static string FormatBindingSource_Header() + => GetString("BindingSource_Header"); + + /// + /// Services + /// + internal static string BindingSource_Services + { + get => GetString("BindingSource_Services"); + } + + /// + /// Services + /// + internal static string FormatBindingSource_Services() + => GetString("BindingSource_Services"); + + /// + /// ModelBinding + /// + internal static string BindingSource_ModelBinding + { + get => GetString("BindingSource_ModelBinding"); + } + + /// + /// ModelBinding + /// + internal static string FormatBindingSource_ModelBinding() + => GetString("BindingSource_ModelBinding"); + + /// + /// Path + /// + internal static string BindingSource_Path + { + get => GetString("BindingSource_Path"); + } + + /// + /// Path + /// + internal static string FormatBindingSource_Path() + => GetString("BindingSource_Path"); + + /// + /// Query + /// + internal static string BindingSource_Query + { + get => GetString("BindingSource_Query"); + } + + /// + /// Query + /// + internal static string FormatBindingSource_Query() + => GetString("BindingSource_Query"); + + /// + /// The provided binding source '{0}' is a composite. '{1}' requires that the source must represent a single type of input. + /// + internal static string BindingSource_CannotBeComposite + { + get => GetString("BindingSource_CannotBeComposite"); + } + + /// + /// The provided binding source '{0}' is a composite. '{1}' requires that the source must represent a single type of input. + /// + internal static string FormatBindingSource_CannotBeComposite(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_CannotBeComposite"), p0, p1); + + /// + /// The provided binding source '{0}' is not a request-based binding source. '{1}' requires that the source must represent data from an HTTP request. + /// + internal static string BindingSource_MustBeFromRequest + { + get => GetString("BindingSource_MustBeFromRequest"); + } + + /// + /// The provided binding source '{0}' is not a request-based binding source. '{1}' requires that the source must represent data from an HTTP request. + /// + internal static string FormatBindingSource_MustBeFromRequest(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_MustBeFromRequest"), p0, p1); + + /// + /// The provided binding source '{0}' is a greedy data source. '{1}' does not support greedy data sources. + /// + internal static string BindingSource_CannotBeGreedy + { + get => GetString("BindingSource_CannotBeGreedy"); + } + + /// + /// The provided binding source '{0}' is a greedy data source. '{1}' does not support greedy data sources. + /// + internal static string FormatBindingSource_CannotBeGreedy(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_CannotBeGreedy"), p0, p1); + + /// + /// The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources. + /// + internal static string BindingSource_MustBeGreedy + { + get => GetString("BindingSource_MustBeGreedy"); + } + + /// + /// The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources. + /// + internal static string FormatBindingSource_MustBeGreedy(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_MustBeGreedy"), p0, p1); + + /// + /// Special + /// + internal static string BindingSource_Special + { + get => GetString("BindingSource_Special"); + } + + /// + /// Special + /// + internal static string FormatBindingSource_Special() + => GetString("BindingSource_Special"); + + /// + /// FormFile + /// + internal static string BindingSource_FormFile + { + get => GetString("BindingSource_FormFile"); + } + + /// + /// FormFile + /// + internal static string FormatBindingSource_FormFile() + => GetString("BindingSource_FormFile"); + + 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/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Resources.resx new file mode 100644 index 0000000000..224ec4161d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Resources.resx @@ -0,0 +1,177 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Value cannot be null or empty. + + + The ModelMetadata property must be set before accessing this property. + + + A field previously marked invalid should not be marked valid. + + + A field previously marked invalid should not be marked skipped. + + + The maximum number of allowed model errors has been reached. + + + Body + + + Custom + + + Form + + + Header + + + Services + + + ModelBinding + + + Path + + + Query + + + The provided binding source '{0}' is a composite. '{1}' requires that the source must represent a single type of input. + + + The provided binding source '{0}' is not a request-based binding source. '{1}' requires that the source must represent data from an HTTP request. + + + The provided binding source '{0}' is a greedy data source. '{1}' does not support greedy data sources. + + + The provided binding source '{0}' is not a greedy data source. '{1}' only supports greedy data sources. + + + Special + + + FormFile + + \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/AttributeRouteInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/AttributeRouteInfo.cs new file mode 100644 index 0000000000..5531b44ced --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/AttributeRouteInfo.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. + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + /// + /// Represents the routing information for an action that is attribute routed. + /// + public class AttributeRouteInfo + { + /// + /// The route template. May be null if the action has no attribute routes. + /// + public string Template { get; set; } + + /// + /// Gets the order of the route associated with a given action. This property determines + /// the order in which routes get executed. Routes with a lower order value are tried first. In case a route + /// doesn't specify a value, it gets a default order of 0. + /// + public int Order { get; set; } + + /// + /// Gets the name of the route associated with a given action. This property can be used + /// to generate a link by referring to the route by name instead of attempting to match a + /// route by provided route data. + /// + public string Name { get; set; } + + /// + /// Gets or sets a value that determines if the route entry associated with this model participates in link generation. + /// + public bool SuppressLinkGeneration { get; set; } + + /// + /// Gets or sets a value that determines if the route entry associated with this model participates in path matching (inbound routing). + /// + public bool SuppressPathMatching { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlActionContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlActionContext.cs new file mode 100644 index 0000000000..8260b6b680 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlActionContext.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. + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + /// + /// Context object to be used for the URLs that generates. + /// + public class UrlActionContext + { + /// + /// The name of the action method that uses to generate URLs. + /// + public string Action + { + get; + set; + } + + /// + /// The name of the controller that uses to generate URLs. + /// + public string Controller + { + get; + set; + } + + /// + /// The object that contains the route values that + /// uses to generate URLs. + /// + public object Values + { + get; + set; + } + + /// + /// The protocol for the URLs that generates, + /// such as "http" or "https" + /// + public string Protocol + { + get; + set; + } + + /// + /// The host name for the URLs that generates. + /// + public string Host + { + get; + set; + } + + /// + /// The fragment for the URLs that generates. + /// + public string Fragment + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlRouteContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlRouteContext.cs new file mode 100644 index 0000000000..706414f958 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlRouteContext.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. + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + /// + /// Context object to be used for the URLs that generates. + /// + public class UrlRouteContext + { + /// + /// The name of the route that uses to generate URLs. + /// + public string RouteName + { + get; + set; + } + + /// + /// The object that contains the route values that + /// uses to generate URLs. + /// + public object Values + { + get; + set; + } + + /// + /// The protocol for the URLs that generates, + /// such as "http" or "https" + /// + public string Protocol + { + get; + set; + } + + /// + /// The host name for the URLs that generates. + /// + public string Host + { + get; + set; + } + + /// + /// The fragment for the URLs that generates. + /// + public string Fragment + { + get; + set; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/baseline.netcore.json new file mode 100644 index 0000000000..a3ee847f2f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/baseline.netcore.json @@ -0,0 +1,9358 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.Mvc.ActionContext", + "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_HttpContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HttpContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Http.HttpContext" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteData", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteData", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteData", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Routing.RouteData" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "httpContext", + "Type": "Microsoft.AspNetCore.Http.HttpContext" + }, + { + "Name": "routeData", + "Type": "Microsoft.AspNetCore.Routing.RouteData" + }, + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "httpContext", + "Type": "Microsoft.AspNetCore.Http.HttpContext" + }, + { + "Name": "routeData", + "Type": "Microsoft.AspNetCore.Routing.RouteData" + }, + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor" + }, + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Action", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.Routing.UrlActionContext" + } + ], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "contentPath", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IsLocalUrl", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RouteUrl", + "Parameters": [ + { + "Name": "routeContext", + "Type": "Microsoft.AspNetCore.Mvc.Routing.UrlRouteContext" + } + ], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Link", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + } + ], + "ReturnType": "System.String", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Routing.AttributeRouteInfo", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Template", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Template", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "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": "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_SuppressLinkGeneration", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SuppressLinkGeneration", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SuppressPathMatching", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SuppressPathMatching", + "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.Routing.UrlActionContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Action", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Action", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Controller", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Controller", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Values", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Values", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Protocol", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Protocol", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Host", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Host", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Fragment", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Fragment", + "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.Routing.UrlRouteContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_RouteName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Values", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Values", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Protocol", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Protocol", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Host", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Host", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Fragment", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Fragment", + "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.ModelBinding.BindingInfo", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BindingSource", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BinderModelName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BinderModelName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BinderType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BinderType", + "Parameters": [ + { + "Name": "value", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyFilterProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PropertyFilterProvider", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RequestPredicate", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RequestPredicate", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetBindingInfo", + "Parameters": [ + { + "Name": "attributes", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo", + "Static": true, + "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", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "System.IEquatable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_DisplayName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Id", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsGreedy", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsFromRequest", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CanAcceptDataFrom", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Equals", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "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": "Method", + "Name": "op_Equality", + "Parameters": [ + { + "Name": "s1", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + }, + { + "Name": "s2", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "op_Inequality", + "Parameters": [ + { + "Name": "s1", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + }, + { + "Name": "s2", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "id", + "Type": "System.String" + }, + { + "Name": "displayName", + "Type": "System.String" + }, + { + "Name": "isGreedy", + "Type": "System.Boolean" + }, + { + "Name": "isFromRequest", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Body", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Custom", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Form", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Header", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "ModelBinding", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Path", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Query", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Services", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Special", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "FormFile", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.CompositeBindingSource", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "bindingSources", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "displayName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.CompositeBindingSource", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingSources", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CanAcceptDataFrom", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.EnumGroupAndName", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Group", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "group", + "Type": "System.String" + }, + { + "Name": "name", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "group", + "Type": "System.String" + }, + { + "Name": "name", + "Type": "System.Func" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IBinderTypeProviderMetadata", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_BinderType", + "Parameters": [], + "ReturnType": "System.Type", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "BindModelAsync", + "Parameters": [ + { + "Name": "bindingContext", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetBinder", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderProviderContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetMetadataForType", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetMetadataForProperties", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_PropertyFilter", + "Parameters": [], + "ReturnType": "System.Func", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IRequestPredicateProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_RequestPredicate", + "Parameters": [], + "ReturnType": "System.Func", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "ContainsPrefix", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetValue", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProviderFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateValueProviderAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderFactoryContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderProviderContext", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateBinder", + "Parameters": [ + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", + "Virtual": true, + "Abstract": true, + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Metadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MetadataProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Services", + "Parameters": [], + "ReturnType": "System.IServiceProvider", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BinderModelName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BinderModelName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BindingSource", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_FieldName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_FieldName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HttpContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpContext", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsTopLevelObject", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsTopLevelObject", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Model", + "Parameters": [], + "ReturnType": "System.Object", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Model", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelMetadata", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelState", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelType", + "Parameters": [], + "ReturnType": "System.Type", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyFilter", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PropertyFilter", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidationState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValidationState", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValueProvider", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EnterNestedScope", + "Parameters": [ + { + "Name": "modelMetadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "fieldName", + "Type": "System.String" + }, + { + "Name": "modelName", + "Type": "System.String" + }, + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext+NestedScope", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EnterNestedScope", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext+NestedScope", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExitNestedScope", + "Parameters": [], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [ + "System.IEquatable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Failed", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Success", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Model", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsModelSet", + "Parameters": [], + "ReturnType": "System.Boolean", + "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": "Method", + "Name": "Equals", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.IEquatable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ToString", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "op_Equality", + "Parameters": [ + { + "Name": "x", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult" + }, + { + "Name": "y", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "op_Inequality", + "Parameters": [ + { + "Name": "x", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult" + }, + { + "Name": "y", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelError", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Exception", + "Parameters": [], + "ReturnType": "System.Exception", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ErrorMessage", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "exception", + "Type": "System.Exception" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "exception", + "Type": "System.Exception" + }, + { + "Name": "errorMessage", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "errorMessage", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelErrorCollection", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Collections.ObjectModel.Collection", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "exception", + "Type": "System.Exception" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "errorMessage", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "System.IEquatable", + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ContainerType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContainerMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MetadataKind", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataKind", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelType", + "Parameters": [], + "ReturnType": "System.Type", + "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", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Identity", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AdditionalValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyDictionary", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelPropertyCollection", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BinderModelName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BinderType", + "Parameters": [], + "ReturnType": "System.Type", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ConvertEmptyStringToNull", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DataTypeName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Description", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayFormatString", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_EditFormatString", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ElementMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_EnumGroupedDisplayNamesAndValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable>", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_EnumNamesAndValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyDictionary", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HasNonDefaultEditFormat", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HtmlEncode", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HideSurroundingHtml", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsBindingAllowed", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsBindingRequired", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsEnum", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsFlagsEnum", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReadOnly", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsRequired", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelBindingMessageProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelBindingMessageProvider", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Int32", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Placeholder", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_NullDisplayText", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyFilterProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ShowForDisplay", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ShowForEdit", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SimpleDisplayProperty", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_TemplateHint", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyValidationFilter", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IPropertyValidationFilter", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidateChildren", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidatorMetadata", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ElementType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsComplexType", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsNullableValueType", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsCollectionType", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsEnumerableType", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReferenceOrNullableType", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_UnderlyingOrModelType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyGetter", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertySetter", + "Parameters": [], + "ReturnType": "System.Action", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetDisplayName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Equals", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "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": "Method", + "Name": "GetMetadataForType", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": 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, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "identity", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity" + } + ], + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "DefaultOrder", + "Parameters": [], + "ReturnType": "System.Int32", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "BaseType": "System.Collections.ObjectModel.ReadOnlyCollection", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "propertyName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "properties", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MaxAllowedErrors", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MaxAllowedErrors", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HasReachedMaxErrors", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ErrorCount", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Keys", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+KeyEnumerable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Values", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+ValueEnumerable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsValid", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidationState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IReadOnlyDictionary", + "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", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "exception", + "Type": "System.Exception" + }, + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryAddModelError", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "exception", + "Type": "System.Exception" + }, + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddModelError", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "errorMessage", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryAddModelError", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "errorMessage", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetFieldValidationState", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetValidationState", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MarkFieldValid", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MarkFieldSkipped", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Merge", + "Parameters": [ + { + "Name": "dictionary", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetModelValue", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "rawValue", + "Type": "System.Object" + }, + { + "Name": "attemptedValue", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetModelValue", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "valueProviderResult", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ClearValidationState", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Clear", + "Parameters": [], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ContainsKey", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IReadOnlyDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Remove", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryGetValue", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry", + "Direction": "Out" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IReadOnlyDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetEnumerator", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+Enumerator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "StartsWithPrefix", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + }, + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "FindKeysWithPrefix", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+PrefixEnumerable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "maxAllowedErrors", + "Type": "System.Int32" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "dictionary", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "DefaultMaxAllowedErrors", + "Parameters": [], + "ReturnType": "System.Int32", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_RawValue", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RawValue", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AttemptedValue", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AttemptedValue", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Errors", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelErrorCollection", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidationState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValidationState", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsContainerNode", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetModelStateForProperty", + "Parameters": [ + { + "Name": "propertyName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Children", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState", + "Visibility": "Public", + "Kind": "Enumeration", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Field", + "Name": "Unvalidated", + "Parameters": [], + "GenericParameter": [], + "Literal": "0" + }, + { + "Kind": "Field", + "Name": "Invalid", + "Parameters": [], + "GenericParameter": [], + "Literal": "1" + }, + { + "Kind": "Field", + "Name": "Valid", + "Parameters": [], + "GenericParameter": [], + "Literal": "2" + }, + { + "Kind": "Field", + "Name": "Skipped", + "Parameters": [], + "GenericParameter": [], + "Literal": "3" + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.TooManyModelErrorsException", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Exception", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "message", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderFactoryContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueProviders", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [ + "System.IEquatable", + "System.Collections.Generic.IEnumerable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Culture", + "Parameters": [], + "ReturnType": "System.Globalization.CultureInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Values", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Primitives.StringValues", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_FirstValue", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Length", + "Parameters": [], + "ReturnType": "System.Int32", + "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": "Equals", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.IEquatable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetHashCode", + "Parameters": [], + "ReturnType": "System.Int32", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ToString", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "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": "op_Explicit", + "Parameters": [ + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult" + } + ], + "ReturnType": "System.String", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "op_Explicit", + "Parameters": [ + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult" + } + ], + "ReturnType": "System.String[]", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "op_Equality", + "Parameters": [ + { + "Name": "x", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult" + }, + { + "Name": "y", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "op_Inequality", + "Parameters": [ + { + "Name": "x", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult" + }, + { + "Name": "y", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "values", + "Type": "Microsoft.Extensions.Primitives.StringValues" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "values", + "Type": "Microsoft.Extensions.Primitives.StringValues" + }, + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "None", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Attributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "metadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "attributes", + "Type": "System.Collections.Generic.IDictionary" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientValidatorItem", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ValidatorMetadata", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Validator", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Validator", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReusable", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsReusable", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "validatorMetadata", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientValidatorProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ModelMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidatorMetadata", + "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": "modelMetadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "items", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddValidation", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidatorProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateValidators", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientValidatorProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidator", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Validate", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContext" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateValidators", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidatorProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IPropertyValidationFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "ShouldValidateEntry", + "Parameters": [ + { + "Name": "entry", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationEntry" + }, + { + "Name": "parentEntry", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationEntry" + } + ], + "ReturnType": "System.Boolean", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "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", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Model", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Container", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "modelMetadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "metadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "container", + "Type": "System.Object" + }, + { + "Name": "model", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MetadataProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "modelMetadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "metadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationResult", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_MemberName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Message", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "memberName", + "Type": "System.String" + }, + { + "Name": "message", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidatorProviderContext", + "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_ValidatorMetadata", + "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": "modelMetadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "items", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationEntry", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Key", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Metadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Model", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "model", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "modelAccessor", + "Type": "System.Func" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "System.Collections.Generic.IDictionary", + "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", + "Parameters": [ + { + "Name": "key", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Item", + "Parameters": [ + { + "Name": "key", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "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": "Add", + "Parameters": [ + { + "Name": "key", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ContainsKey", + "Parameters": [ + { + "Name": "key", + "Type": "System.Object" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Remove", + "Parameters": [ + { + "Name": "key", + "Type": "System.Object" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryGetValue", + "Parameters": [ + { + "Name": "key", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry", + "Direction": "Out" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry", + "Visibility": "Public", + "Kind": "Class", + "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": "Method", + "Name": "get_Metadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Metadata", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SuppressValidation", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SuppressValidation", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Strategy", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Strategy", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidatorItem", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ValidatorMetadata", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Validator", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Validator", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidator" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReusable", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsReusable", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "validatorMetadata", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelBindingMessageProvider", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_MissingBindRequiredValueAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MissingKeyOrValueAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MissingRequestBodyRequiredValueAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueMustNotBeNullAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AttemptedValueIsInvalidAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_NonPropertyAttemptedValueIsInvalidAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_UnknownValueIsInvalidAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_NonPropertyUnknownValueIsInvalidAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueIsInvalidAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueMustBeANumberAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_NonPropertyValueMustBeANumberAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [ + "System.IEquatable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ForType", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ForProperty", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "containerType", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity", + "Static": true, + "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", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MetadataKind", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataKind", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ParameterInfo", + "Parameters": [], + "ReturnType": "System.Reflection.ParameterInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Equals", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity" + } + ], + "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": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataKind", + "Visibility": "Public", + "Kind": "Enumeration", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Field", + "Name": "Type", + "Parameters": [], + "GenericParameter": [], + "Literal": "0" + }, + { + "Kind": "Field", + "Name": "Property", + "Parameters": [], + "GenericParameter": [], + "Literal": "1" + }, + { + "Kind": "Field", + "Name": "Parameter", + "Parameters": [], + "GenericParameter": [], + "Literal": "2" + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.FormatterCollection", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Collections.ObjectModel.Collection", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "formatterType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "list", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TFormatter", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CanRead", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + } + ], + "ReturnType": "System.Boolean", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReadAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_TreatEmptyInputAsDefaultValue", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HttpContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Metadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ReaderFactory", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "httpContext", + "Type": "Microsoft.AspNetCore.Http.HttpContext" + }, + { + "Name": "modelName", + "Type": "System.String" + }, + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + }, + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "readerFactory", + "Type": "System.Func" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "httpContext", + "Type": "Microsoft.AspNetCore.Http.HttpContext" + }, + { + "Name": "modelName", + "Type": "System.String" + }, + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + }, + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "readerFactory", + "Type": "System.Func" + }, + { + "Name": "treatEmptyInputAsDefaultValue", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_HasError", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsModelSet", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Model", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Failure", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterResult", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "FailureAsync", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Success", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterResult", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SuccessAsync", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "NoValue", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterResult", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "NoValueAsync", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CanWriteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterCanWriteContext" + } + ], + "ReturnType": "System.Boolean", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterCanWriteContext", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_HttpContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpContext", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HttpContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Http.HttpContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContentType", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Primitives.StringSegment", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ContentType", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.Extensions.Primitives.StringSegment" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContentTypeIsServerDefined", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ContentTypeIsServerDefined", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Object", + "Parameters": [], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Object", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ObjectType", + "Parameters": [], + "ReturnType": "System.Type", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ObjectType", + "Parameters": [ + { + "Name": "value", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "httpContext", + "Type": "Microsoft.AspNetCore.Http.HttpContext" + } + ], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterCanWriteContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_WriterFactory", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_WriterFactory", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "httpContext", + "Type": "Microsoft.AspNetCore.Http.HttpContext" + }, + { + "Name": "writerFactory", + "Type": "System.Func" + }, + { + "Name": "objectType", + "Type": "System.Type" + }, + { + "Name": "object", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Canceled", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Canceled", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Controller", + "Parameters": [], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Exception", + "Parameters": [], + "ReturnType": "System.Exception", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Exception", + "Parameters": [ + { + "Name": "value", + "Type": "System.Exception" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExceptionDispatchInfo", + "Parameters": [], + "ReturnType": "System.Runtime.ExceptionServices.ExceptionDispatchInfo", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ExceptionDispatchInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Runtime.ExceptionServices.ExceptionDispatchInfo" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExceptionHandled", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ExceptionHandled", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IActionResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "controller", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IActionResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ActionArguments", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Controller", + "Parameters": [], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "actionArguments", + "Type": "System.Collections.Generic.IDictionary" + }, + { + "Name": "controller", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutionDelegate", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.MulticastDelegate", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Invoke", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BeginInvoke", + "Parameters": [ + { + "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.Filters.AuthorizationFilterContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IActionResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ExceptionContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Exception", + "Parameters": [], + "ReturnType": "System.Exception", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Exception", + "Parameters": [ + { + "Name": "value", + "Type": "System.Exception" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExceptionDispatchInfo", + "Parameters": [], + "ReturnType": "System.Runtime.ExceptionServices.ExceptionDispatchInfo", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ExceptionDispatchInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Runtime.ExceptionServices.ExceptionDispatchInfo" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExceptionHandled", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ExceptionHandled", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IActionResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "Microsoft.AspNetCore.Mvc.ActionContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Filters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.FilterDescriptor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Filter", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "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": "get_Scope", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "filter", + "Type": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + }, + { + "Name": "filterScope", + "Type": "System.Int32" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.FilterItem", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Descriptor", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.FilterDescriptor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Filter", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Filter", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReusable", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsReusable", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterDescriptor" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterDescriptor" + }, + { + "Name": "filter", + "Type": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.FilterProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Results", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Results", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "items", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "OnActionExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnActionExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "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", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "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", + "GenericParameter": [] + } + ], + "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", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "OnAuthorizationAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IAsyncExceptionFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "OnExceptionAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IAsyncResourceFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "OnResourceExecutionAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext" + }, + { + "Name": "next", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutionDelegate" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "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", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IAuthorizationFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "OnAuthorization", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IExceptionFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "OnException", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IFilterContainer", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_FilterDefinition", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_FilterDefinition", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_IsReusable", + "Parameters": [], + "ReturnType": "System.Boolean", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateInstance", + "Parameters": [ + { + "Name": "serviceProvider", + "Type": "System.IServiceProvider" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IFilterProvider", + "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.Filters.FilterProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Int32", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "OnResourceExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnResourceExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutedContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IResultFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "OnResultExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnResultExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutedContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutedContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Canceled", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Canceled", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Exception", + "Parameters": [], + "ReturnType": "System.Exception", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Exception", + "Parameters": [ + { + "Name": "value", + "Type": "System.Exception" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExceptionDispatchInfo", + "Parameters": [], + "ReturnType": "System.Runtime.ExceptionServices.ExceptionDispatchInfo", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ExceptionDispatchInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Runtime.ExceptionServices.ExceptionDispatchInfo" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExceptionHandled", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ExceptionHandled", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IActionResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IActionResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueProviderFactories", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "valueProviderFactories", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutionDelegate", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.MulticastDelegate", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Invoke", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BeginInvoke", + "Parameters": [ + { + "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.Filters.ResultExecutedContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Canceled", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Canceled", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Controller", + "Parameters": [], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Exception", + "Parameters": [], + "ReturnType": "System.Exception", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Exception", + "Parameters": [ + { + "Name": "value", + "Type": "System.Exception" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExceptionDispatchInfo", + "Parameters": [], + "ReturnType": "System.Runtime.ExceptionServices.ExceptionDispatchInfo", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ExceptionDispatchInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Runtime.ExceptionServices.ExceptionDispatchInfo" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExceptionHandled", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ExceptionHandled", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.IActionResult" + }, + { + "Name": "controller", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Controller", + "Parameters": [], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IActionResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Cancel", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Cancel", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.IActionResult" + }, + { + "Name": "controller", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutionDelegate", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.MulticastDelegate", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Invoke", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BeginInvoke", + "Parameters": [ + { + "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.Authorization.IAllowAnonymousFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [], + "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Candidates", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Candidates", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_CurrentCandidate", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionSelectorCandidate", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_CurrentCandidate", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionSelectorCandidate" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Routing.RouteContext" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintItem", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Constraint", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Constraint", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Metadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraintMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReusable", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsReusable", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraintMetadata" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_HttpContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Action", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Results", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Http.HttpContext" + }, + { + "Name": "action", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor" + }, + { + "Name": "items", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionSelectorCandidate", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Action", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Constraints", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "action", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor" + }, + { + "Name": "constraints", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraintMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Int32", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Accept", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintContext" + } + ], + "ReturnType": "System.Boolean", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraintFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraintMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_IsReusable", + "Parameters": [], + "ReturnType": "System.Boolean", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateInstance", + "Parameters": [ + { + "Name": "services", + "Type": "System.IServiceProvider" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraintMetadata", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraintProvider", + "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.ActionConstraints.ActionConstraintProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Id", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteValues", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AttributeRouteInfo", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Routing.AttributeRouteInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AttributeRouteInfo", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Routing.AttributeRouteInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ActionConstraints", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionConstraints", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Parameters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Parameters", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BoundProperties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BoundProperties", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_FilterDescriptors", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_FilterDescriptors", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DisplayName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Properties", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptorExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetProperty", + "Parameters": [ + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor" + } + ], + "ReturnType": "T0", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "SetProperty", + "Parameters": [ + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor" + }, + { + "Name": "value", + "Type": "T0" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptorProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Results", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Abstractions.ActionInvokerProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvoker", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvoker" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Abstractions.IActionDescriptorProvider", + "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.Abstractions.ActionDescriptorProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptorProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvoker", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "InvokeAsync", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvokerProvider", + "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.Abstractions.ActionInvokerProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionInvokerProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "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_ParameterType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ParameterType", + "Parameters": [ + { + "Name": "value", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingInfo", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BindingInfo", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext+NestedScope", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [ + "System.IDisposable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Dispose", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.IDisposable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+PrefixEnumerable", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerable>" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetEnumerator", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+Enumerator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "dictionary", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + }, + { + "Name": "prefix", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+Enumerator", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerator>" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Dispose", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.IDisposable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MoveNext", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.IEnumerator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Reset", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.IEnumerator", + "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", + "Parameters": [ + { + "Name": "dictionary", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + }, + { + "Name": "prefix", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+KeyEnumerable", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetEnumerator", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+KeyEnumerator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "dictionary", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+KeyEnumerator", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerator" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Dispose", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.IDisposable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MoveNext", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.IEnumerator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Reset", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.IEnumerator", + "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", + "Parameters": [ + { + "Name": "dictionary", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + }, + { + "Name": "prefix", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+ValueEnumerable", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetEnumerator", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+ValueEnumerator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "dictionary", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary+ValueEnumerator", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerator" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Dispose", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.IDisposable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MoveNext", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.IEnumerator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Reset", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.IEnumerator", + "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", + "Parameters": [ + { + "Name": "dictionary", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + }, + { + "Name": "prefix", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs new file mode 100644 index 0000000000..90b2032abb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.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.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(DiagnosticDescriptors.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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs new file mode 100644 index 0000000000..f36c18d0e6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.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.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(DiagnosticDescriptors.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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs new file mode 100644 index 0000000000..59a8eec020 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.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 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(DiagnosticDescriptors.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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs new file mode 100644 index 0000000000..e97e593a9f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.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.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(DiagnosticDescriptors.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 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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs new file mode 100644 index 0000000000..67eb82bd2b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs @@ -0,0 +1,94 @@ +// Copyright (c) .NET 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(DiagnosticDescriptors.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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider.cs new file mode 100644 index 0000000000..3637c47217 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider.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.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(DiagnosticDescriptors.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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs new file mode 100644 index 0000000000..ef00b99527 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.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; +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(DiagnosticDescriptors.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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs new file mode 100644 index 0000000000..792b47227b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.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.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(DiagnosticDescriptors.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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerBase.cs new file mode 100644 index 0000000000..b70d1409de --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerBase.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.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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerContext.cs new file mode 100644 index 0000000000..1ab36e3dac --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerContext.cs @@ -0,0 +1,64 @@ +// Copyright (c) .NET 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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/CodeAnalysisExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/CodeAnalysisExtensions.cs new file mode 100644 index 0000000000..a8ae5b8a0f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/CodeAnalysisExtensions.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.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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerBase.cs new file mode 100644 index 0000000000..71c497fa29 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerBase.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.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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerContext.cs new file mode 100644 index 0000000000..8a9d403a8a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerContext.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 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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs new file mode 100644 index 0000000000..7c8100234c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.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 Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public static class DiagnosticDescriptors + { + 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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj new file mode 100644 index 0000000000..62ac6da6a1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj @@ -0,0 +1,25 @@ + + + CSharp Analyzers for ASP.NET Core MVC. + aspnetcore;aspnetcoremvc + + false + $(ExperimentalVersionPrefix) + $(ExperimentalVersionSuffix) + $(ExperimentalPackageVersion) + + netstandard1.3 + false + false + false + + + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/TypeNames.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/TypeNames.cs new file mode 100644 index 0000000000..810c63a7bd --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/TypeNames.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. + +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/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/AvoidHtmlPartialAnalyzer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/AvoidHtmlPartialAnalyzer.cs new file mode 100644 index 0000000000..3563fa525b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/AvoidHtmlPartialAnalyzer.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; +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 AvoidHtmlPartialAnalyzer : ViewFeatureAnalyzerBase + { + public AvoidHtmlPartialAnalyzer() + : base(DiagnosticDescriptors.MVC1000_HtmlHelperPartialShouldBeAvoided) + { + } + + protected override void InitializeWorker(ViewFeaturesAnalyzerContext analyzerContext) + { + analyzerContext.Context.RegisterSyntaxNodeAction(context => + { + var invocationExpression = (InvocationExpressionSyntax)context.Node; + var symbol = context.SemanticModel.GetSymbolInfo(invocationExpression, context.CancellationToken).Symbol; + if (symbol == null || symbol.Kind != SymbolKind.Method) + { + return; + } + + var method = (IMethodSymbol)symbol; + if (!analyzerContext.IsHtmlHelperExtensionMethod(method)) + { + return; + } + + if (string.Equals(SymbolNames.PartialMethod, method.Name, StringComparison.Ordinal)) + { + context.ReportDiagnostic(Diagnostic.Create( + SupportedDiagnostic, + invocationExpression.GetLocation(), + new[] { SymbolNames.PartialMethod })); + } + else if (string.Equals(SymbolNames.RenderPartialMethod, method.Name, StringComparison.Ordinal)) + { + context.ReportDiagnostic(Diagnostic.Create( + SupportedDiagnostic, + invocationExpression.GetLocation(), + new[] { SymbolNames.RenderPartialMethod })); + } + }, SyntaxKind.InvocationExpression); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs new file mode 100644 index 0000000000..89465662b4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.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 Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public static class DiagnosticDescriptors + { + public static readonly DiagnosticDescriptor MVC1000_HtmlHelperPartialShouldBeAvoided = + new DiagnosticDescriptor( + "MVC1000", + "Use of IHtmlHelper.{0} should be avoided.", + "Use of IHtmlHelper.{0} may result in application deadlocks. Consider using Tag Helper or IHtmlHelper.{0}Async.", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj new file mode 100644 index 0000000000..310c88685a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj @@ -0,0 +1,42 @@ + + + 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/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.nuspec b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.nuspec new file mode 100644 index 0000000000..5f9d436f73 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.nuspec @@ -0,0 +1,24 @@ + + + + $id$ + $version$ + $authors$ + true + $licenseUrl$ + $projectUrl$ + $iconUrl$ + $description$ + $copyright$ + $tags$ + + + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs new file mode 100644 index 0000000000..9c49dd24e6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.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. + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + internal static class SymbolNames + { + public const string IHtmlHelperType = "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper"; + + public const string HtmlHelperPartialExtensionsType = "Microsoft.AspNetCore.Mvc.Rendering.HtmlHelperPartialExtensions"; + + public const string PartialMethod = "Partial"; + + public const string RenderPartialMethod = "RenderPartial"; + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeatureAnalyzerBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeatureAnalyzerBase.cs new file mode 100644 index 0000000000..bade87c8a4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeatureAnalyzerBase.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 Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public abstract class ViewFeatureAnalyzerBase : DiagnosticAnalyzer + { + public ViewFeatureAnalyzerBase(DiagnosticDescriptor diagnosticDescriptor) + { + SupportedDiagnostic = diagnosticDescriptor; + SupportedDiagnostics = ImmutableArray.Create(new[] { SupportedDiagnostic }); + } + + protected DiagnosticDescriptor SupportedDiagnostic { get; } + + public override ImmutableArray SupportedDiagnostics { get; } + + public sealed override void Initialize(AnalysisContext context) + { + context.RegisterCompilationStartAction(compilationContext => + { + var analyzerContext = new ViewFeaturesAnalyzerContext(compilationContext); + + // Only do work if we can locate IHtmlHelper. + if (analyzerContext.HtmlHelperType == null) + { + return; + } + + InitializeWorker(analyzerContext); + }); + } + + protected abstract void InitializeWorker(ViewFeaturesAnalyzerContext analyzerContext); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeaturesAnalyzerContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeaturesAnalyzerContext.cs new file mode 100644 index 0000000000..096132d67f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeaturesAnalyzerContext.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; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class ViewFeaturesAnalyzerContext + { +#pragma warning disable RS1012 // Start action has no registered actions. + public ViewFeaturesAnalyzerContext(CompilationStartAnalysisContext context) +#pragma warning restore RS1012 // Start action has no registered actions. + { + Context = context; + HtmlHelperType = GetType(SymbolNames.IHtmlHelperType); + HtmlHelperPartialExtensionsType = GetType(SymbolNames.HtmlHelperPartialExtensionsType); + } + + public CompilationStartAnalysisContext Context { get; } + + public INamedTypeSymbol HtmlHelperType { get; } + + public INamedTypeSymbol HtmlHelperPartialExtensionsType { get; } + + private INamedTypeSymbol GetType(string name) => Context.Compilation.GetTypeByMetadataName(name); + + public bool IsHtmlHelperExtensionMethod(IMethodSymbol method) + { + if (!method.IsExtensionMethod) + { + return false; + } + + if (method.ReceiverType != HtmlHelperType) + { + return false; + } + + if (method.ContainingType != HtmlHelperPartialExtensionsType) + { + return false; + } + + return true; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionExtensions.cs new file mode 100644 index 0000000000..d8c99a8b58 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionExtensions.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET 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 +{ + /// + /// Extension methods for . + /// + public static class ApiDescriptionExtensions + { + /// + /// Gets the value of a property from the collection + /// using the provided value of as the key. + /// + /// The type of the property. + /// The . + /// The property or the default value of . + public static T GetProperty(this ApiDescription apiDescription) + { + if (apiDescription == null) + { + throw new ArgumentNullException(nameof(apiDescription)); + } + + object value; + if (apiDescription.Properties.TryGetValue(typeof(T), out value)) + { + return (T)value; + } + else + { + return default(T); + } + } + + /// + /// Sets the value of an property in the collection using + /// the provided value of as the key. + /// + /// The type of the property. + /// The . + /// The value of the property. + public static void SetProperty(this ApiDescription apiDescription, T value) + { + if (apiDescription == null) + { + throw new ArgumentNullException(nameof(apiDescription)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + apiDescription.Properties[typeof(T)] = value; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroup.cs new file mode 100644 index 0000000000..3a8e35e578 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroup.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Represents a group of related apis. + /// + public class ApiDescriptionGroup + { + /// + /// Creates a new . + /// + /// The group name. + /// A collection of items for this group. + public ApiDescriptionGroup(string groupName, IReadOnlyList items) + { + GroupName = groupName; + Items = items; + } + + /// + /// The group name. + /// + public string GroupName { get; } + + /// + /// A collection of items for this group. + /// + public IReadOnlyList Items { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollection.cs new file mode 100644 index 0000000000..b071f1c2c9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollection.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// A cached collection of . + /// + public class ApiDescriptionGroupCollection + { + /// + /// Initializes a new instance of the . + /// + /// The list of . + /// The unique version of discovered groups. + public ApiDescriptionGroupCollection(IReadOnlyList items, int version) + { + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + Items = items; + Version = version; + } + + /// + /// Returns the list of . + /// + public IReadOnlyList Items { get; } + + /// + /// Returns the unique version of the current items. + /// + public int Version { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollectionProvider.cs new file mode 100644 index 0000000000..1d7976d5bd --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollectionProvider.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 System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + public class ApiDescriptionGroupCollectionProvider : IApiDescriptionGroupCollectionProvider + { + private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; + private readonly IApiDescriptionProvider[] _apiDescriptionProviders; + + private ApiDescriptionGroupCollection _apiDescriptionGroups; + + /// + /// Creates a new instance of . + /// + /// + /// The . + /// + /// + /// The . + /// + public ApiDescriptionGroupCollectionProvider( + IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, + IEnumerable apiDescriptionProviders) + { + _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; + _apiDescriptionProviders = apiDescriptionProviders.OrderBy(item => item.Order).ToArray(); + } + + /// + public ApiDescriptionGroupCollection ApiDescriptionGroups + { + get + { + var actionDescriptors = _actionDescriptorCollectionProvider.ActionDescriptors; + if (_apiDescriptionGroups == null || _apiDescriptionGroups.Version != actionDescriptors.Version) + { + _apiDescriptionGroups = GetCollection(actionDescriptors); + } + + return _apiDescriptionGroups; + } + } + + private ApiDescriptionGroupCollection GetCollection(ActionDescriptorCollection actionDescriptors) + { + var context = new ApiDescriptionProviderContext(actionDescriptors.Items); + + foreach (var provider in _apiDescriptionProviders) + { + provider.OnProvidersExecuting(context); + } + + for (var i = _apiDescriptionProviders.Length - 1; i >= 0; i--) + { + _apiDescriptionProviders[i].OnProvidersExecuted(context); + } + + var groups = context.Results + .GroupBy(d => d.GroupName) + .Select(g => new ApiDescriptionGroup(g.Key, g.ToArray())) + .ToArray(); + + return new ApiDescriptionGroupCollection(groups, actionDescriptors.Version); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs new file mode 100644 index 0000000000..2ed34e730a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -0,0 +1,812 @@ +// Copyright (c) .NET 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.Controllers; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Template; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Implements a provider of for actions represented + /// by . + /// + public class DefaultApiDescriptionProvider : IApiDescriptionProvider + { + private readonly MvcOptions _mvcOptions; + private readonly IActionResultTypeMapper _mapper; + private readonly IInlineConstraintResolver _constraintResolver; + private readonly IModelMetadataProvider _modelMetadataProvider; + + /// + /// Creates a new instance of . + /// + /// The accessor for . + /// The used for resolving inline + /// constraints. + /// The . + [Obsolete("This constructor is obsolete and will be removed in a future release.")] + public DefaultApiDescriptionProvider( + IOptions optionsAccessor, + IInlineConstraintResolver constraintResolver, + IModelMetadataProvider modelMetadataProvider) + { + _mvcOptions = optionsAccessor.Value; + _constraintResolver = constraintResolver; + _modelMetadataProvider = modelMetadataProvider; + } + + /// + /// Creates a new instance of . + /// + /// The accessor for . + /// The used for resolving inline + /// constraints. + /// The . + /// The . + public DefaultApiDescriptionProvider( + IOptions optionsAccessor, + IInlineConstraintResolver constraintResolver, + IModelMetadataProvider modelMetadataProvider, + IActionResultTypeMapper mapper) + { + _mvcOptions = optionsAccessor.Value; + _constraintResolver = constraintResolver; + _modelMetadataProvider = modelMetadataProvider; + _mapper = mapper; + } + + /// + public int Order => -1000; + + /// + public void OnProvidersExecuting(ApiDescriptionProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + foreach (var action in context.Actions.OfType()) + { + if (action.AttributeRouteInfo != null && action.AttributeRouteInfo.SuppressPathMatching) + { + continue; + } + + var extensionData = action.GetProperty(); + if (extensionData != null) + { + var httpMethods = GetHttpMethods(action); + foreach (var httpMethod in httpMethods) + { + context.Results.Add(CreateApiDescription(action, httpMethod, extensionData.GroupName)); + } + } + } + } + + public void OnProvidersExecuted(ApiDescriptionProviderContext context) + { + } + + private ApiDescription CreateApiDescription( + ControllerActionDescriptor action, + string httpMethod, + string groupName) + { + var parsedTemplate = ParseTemplate(action); + + var apiDescription = new ApiDescription() + { + ActionDescriptor = action, + GroupName = groupName, + HttpMethod = httpMethod, + RelativePath = GetRelativePath(parsedTemplate), + }; + + var templateParameters = parsedTemplate?.Parameters?.ToList() ?? new List(); + + var parameterContext = new ApiParameterContext(_modelMetadataProvider, action, templateParameters); + + foreach (var parameter in GetParameters(parameterContext)) + { + apiDescription.ParameterDescriptions.Add(parameter); + } + + 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); + foreach (var apiResponseType in apiResponseTypes) + { + apiDescription.SupportedResponseTypes.Add(apiResponseType); + } + + // It would be possible here to configure an action with multiple body parameters, in which case you + // could end up with duplicate data. + if (apiDescription.ParameterDescriptions.Count > 0) + { + var contentTypes = GetDeclaredContentTypes(requestMetadataAttributes); + foreach (var parameter in apiDescription.ParameterDescriptions) + { + if (parameter.Source == BindingSource.Body) + { + // For request body bound parameters, determine the content types supported + // by input formatters. + var requestFormats = GetSupportedFormats(contentTypes, parameter.Type); + foreach (var format in requestFormats) + { + apiDescription.SupportedRequestFormats.Add(format); + } + } + else if (parameter.Source == BindingSource.FormFile) + { + // Add all declared media types since FormFiles do not get processed by formatters. + foreach (var contentType in contentTypes) + { + apiDescription.SupportedRequestFormats.Add(new ApiRequestFormat + { + MediaType = contentType, + }); + } + } + } + } + + return apiDescription; + } + + private IList GetParameters(ApiParameterContext context) + { + // First, get parameters from the model-binding/parameter-binding side of the world. + if (context.ActionDescriptor.Parameters != null) + { + foreach (var actionParameter in context.ActionDescriptor.Parameters) + { + var visitor = new PseudoModelBindingVisitor(context, actionParameter); + + ModelMetadata metadata = null; + if (_mvcOptions.AllowValidatingTopLevelNodes && + actionParameter is ControllerParameterDescriptor controllerParameterDescriptor && + _modelMetadataProvider is ModelMetadataProvider provider) + { + // The default model metadata provider derives from ModelMetadataProvider + // and can therefore supply information about attributes applied to parameters. + metadata = provider.GetMetadataForParameter(controllerParameterDescriptor.ParameterInfo); + } + else + { + // For backward compatibility, if there's a custom model metadata provider that + // only implements the older IModelMetadataProvider interface, access the more + // limited metadata information it supplies. In this scenario, validation attributes + // are not supported on parameters. + metadata = _modelMetadataProvider.GetMetadataForType(actionParameter.ParameterType); + } + + var bindingContext = ApiParameterDescriptionContext.GetContext( + metadata, + actionParameter.BindingInfo, + propertyName: actionParameter.Name); + visitor.WalkParameter(bindingContext); + } + } + + if (context.ActionDescriptor.BoundProperties != null) + { + foreach (var actionParameter in context.ActionDescriptor.BoundProperties) + { + var visitor = new PseudoModelBindingVisitor(context, actionParameter); + var modelMetadata = context.MetadataProvider.GetMetadataForProperty( + containerType: context.ActionDescriptor.ControllerTypeInfo.AsType(), + propertyName: actionParameter.Name); + + var bindingContext = ApiParameterDescriptionContext.GetContext( + modelMetadata, + actionParameter.BindingInfo, + propertyName: actionParameter.Name); + + visitor.WalkParameter(bindingContext); + } + } + + for (var i = context.Results.Count - 1; i >= 0; i--) + { + // Remove any 'hidden' parameters. These are things that can't come from user input, + // so they aren't worth showing. + if (!context.Results[i].Source.IsFromRequest) + { + context.Results.RemoveAt(i); + } + } + + // Next, we want to join up any route parameters with those discovered from the action's parameters. + var routeParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var routeParameter in context.RouteParameters) + { + routeParameters.Add(routeParameter.Name, CreateRouteInfo(routeParameter)); + } + + foreach (var parameter in context.Results) + { + if (parameter.Source == BindingSource.Path || + parameter.Source == BindingSource.ModelBinding || + parameter.Source == BindingSource.Custom) + { + if (routeParameters.TryGetValue(parameter.Name, out var routeInfo)) + { + parameter.RouteInfo = routeInfo; + routeParameters.Remove(parameter.Name); + + if (parameter.Source == BindingSource.ModelBinding && + !parameter.RouteInfo.IsOptional) + { + // If we didn't see any information about the parameter, but we have + // a route parameter that matches, let's switch it to path. + parameter.Source = BindingSource.Path; + } + } + } + } + + // Lastly, create a parameter representation for each route parameter that did not find + // a partner. + foreach (var routeParameter in routeParameters) + { + context.Results.Add(new ApiParameterDescription() + { + Name = routeParameter.Key, + RouteInfo = routeParameter.Value, + Source = BindingSource.Path, + }); + } + + return context.Results; + } + + private ApiParameterRouteInfo CreateRouteInfo(TemplatePart routeParameter) + { + var constraints = new List(); + if (routeParameter.InlineConstraints != null) + { + foreach (var constraint in routeParameter.InlineConstraints) + { + constraints.Add(_constraintResolver.ResolveConstraint(constraint.Constraint)); + } + } + + return new ApiParameterRouteInfo() + { + Constraints = constraints, + DefaultValue = routeParameter.DefaultValue, + IsOptional = routeParameter.IsOptional || routeParameter.DefaultValue != null, + }; + } + + private IEnumerable GetHttpMethods(ControllerActionDescriptor action) + { + if (action.ActionConstraints != null && action.ActionConstraints.Count > 0) + { + return action.ActionConstraints.OfType().SelectMany(c => c.HttpMethods); + } + else + { + return new string[] { null }; + } + } + + private RouteTemplate ParseTemplate(ControllerActionDescriptor action) + { + if (action.AttributeRouteInfo?.Template != null) + { + return TemplateParser.Parse(action.AttributeRouteInfo.Template); + } + + return null; + } + + private string GetRelativePath(RouteTemplate parsedTemplate) + { + if (parsedTemplate == null) + { + return null; + } + + var segments = new List(); + + foreach (var segment in parsedTemplate.Segments) + { + var currentSegment = string.Empty; + foreach (var part in segment.Parts) + { + if (part.IsLiteral) + { + currentSegment += part.Text; + } + else if (part.IsParameter) + { + currentSegment += "{" + part.Name + "}"; + } + } + + segments.Add(currentSegment); + } + + return string.Join("/", segments); + } + + private IReadOnlyList GetSupportedFormats(MediaTypeCollection contentTypes, Type type) + { + if (contentTypes.Count == 0) + { + contentTypes = new MediaTypeCollection + { + (string)null, + }; + } + + var results = new List(); + foreach (var contentType in contentTypes) + { + foreach (var formatter in _mvcOptions.InputFormatters) + { + if (formatter is IApiRequestFormatMetadataProvider requestFormatMetadataProvider) + { + var supportedTypes = requestFormatMetadataProvider.GetSupportedContentTypes(contentType, type); + + if (supportedTypes != null) + { + foreach (var supportedType in supportedTypes) + { + results.Add(new ApiRequestFormat() + { + Formatter = formatter, + MediaType = supportedType, + }); + } + } + } + } + } + + return results; + } + + private static MediaTypeCollection GetDeclaredContentTypes(IApiRequestMetadataProvider[] requestMetadataAttributes) + { + // 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 (requestMetadataAttributes != null) + { + foreach (var metadataAttribute in requestMetadataAttributes) + { + metadataAttribute.SetContentTypes(contentTypes); + } + } + + 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 == 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) + { + 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) + { + return null; + } + + // This technique for enumerating filters will intentionally ignore any filter that is an IFilterFactory + // while searching for a filter that implements IApiRequestMetadataProvider. + // + // The workaround for that is to implement the metadata interface on the IFilterFactory. + return action.FilterDescriptors + .Select(fd => fd.Filter) + .OfType() + .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( + IModelMetadataProvider metadataProvider, + ControllerActionDescriptor actionDescriptor, + IReadOnlyList routeParameters) + { + MetadataProvider = metadataProvider; + ActionDescriptor = actionDescriptor; + RouteParameters = routeParameters; + + Results = new List(); + } + + public ControllerActionDescriptor ActionDescriptor { get; } + + public IModelMetadataProvider MetadataProvider { get; } + + public IList Results { get; } + + public IReadOnlyList RouteParameters { get; } + } + + private class ApiParameterDescriptionContext + { + public ModelMetadata ModelMetadata { get; set; } + + public string BinderModelName { get; set; } + + public BindingSource BindingSource { get; set; } + + public string PropertyName { get; set; } + + public static ApiParameterDescriptionContext GetContext( + ModelMetadata metadata, + BindingInfo bindingInfo, + string propertyName) + { + // BindingMetadata can be null if the metadata represents properties. + return new ApiParameterDescriptionContext + { + ModelMetadata = metadata, + BinderModelName = bindingInfo?.BinderModelName, + BindingSource = bindingInfo?.BindingSource, + PropertyName = propertyName ?? metadata.Name, + }; + } + } + + private class PseudoModelBindingVisitor + { + public PseudoModelBindingVisitor(ApiParameterContext context, ParameterDescriptor parameter) + { + Context = context; + Parameter = parameter; + + Visited = new HashSet(new PropertyKeyEqualityComparer()); + } + + public ApiParameterContext Context { get; } + + public ParameterDescriptor Parameter { get; } + + // Avoid infinite recursion by tracking properties. + private HashSet Visited { get; } + + public void WalkParameter(ApiParameterDescriptionContext context) + { + // Attempt to find a binding source for the parameter + // + // The default is ModelBinding (aka all default value providers) + var source = BindingSource.ModelBinding; + Visit(context, source, containerName: string.Empty); + } + + private void Visit( + ApiParameterDescriptionContext bindingContext, + BindingSource ambientSource, + string containerName) + { + var source = bindingContext.BindingSource; + if (source != null && source.IsGreedy) + { + // We have a definite answer for this model. This is a greedy source like + // [FromBody] so there's no need to consider properties. + Context.Results.Add(CreateResult(bindingContext, source, containerName)); + return; + } + + var modelMetadata = bindingContext.ModelMetadata; + + // For any property which is a leaf node, we don't want to keep traversing: + // + // 1) Collections - while it's possible to have binder attributes on the inside of a collection, + // it hardly seems useful, and would result in some very weird binding. + // + // 2) Simple Types - These are generally part of the .net framework - primitives, or types which have a + // type converter from string. + // + // 3) Types with no properties. Obviously nothing to explore there. + // + if (modelMetadata.IsEnumerableType || + !modelMetadata.IsComplexType || + modelMetadata.Properties.Count == 0) + { + Context.Results.Add(CreateResult(bindingContext, source ?? ambientSource, containerName)); + return; + } + + // This will come from composite model binding - so investigate what's going on with each property. + // + // Ex: + // + // public IActionResult PlaceOrder(OrderDTO order) {...} + // + // public class OrderDTO + // { + // public int AccountId { get; set; } + // + // [FromBody] + // public Order { get; set; } + // } + // + // This should result in two parameters: + // + // AccountId - source: Any + // Order - source: Body + // + + // We don't want to append the **parameter** name when building a model name. + var newContainerName = containerName; + if (modelMetadata.ContainerType != null) + { + newContainerName = GetName(containerName, bindingContext); + } + + for (var i = 0; i < modelMetadata.Properties.Count; i++) + { + var propertyMetadata = modelMetadata.Properties[i]; + var key = new PropertyKey(propertyMetadata, source); + var bindingInfo = BindingInfo.GetBindingInfo(Enumerable.Empty(), propertyMetadata); + + var propertyContext = ApiParameterDescriptionContext.GetContext( + propertyMetadata, + bindingInfo: bindingInfo, + propertyName: null); + + if (Visited.Add(key)) + { + Visit(propertyContext, source ?? ambientSource, newContainerName); + } + else + { + // This is cycle, so just add a result rather than traversing. + Context.Results.Add(CreateResult(propertyContext, source ?? ambientSource, newContainerName)); + } + } + } + + private ApiParameterDescription CreateResult( + ApiParameterDescriptionContext bindingContext, + BindingSource source, + string containerName) + { + return new ApiParameterDescription() + { + ModelMetadata = bindingContext.ModelMetadata, + Name = GetName(containerName, bindingContext), + Source = source, + Type = bindingContext.ModelMetadata.ModelType, + ParameterDescriptor = Parameter, + }; + } + + private static string GetName(string containerName, ApiParameterDescriptionContext metadata) + { + if (!string.IsNullOrEmpty(metadata.BinderModelName)) + { + // Name was explicitly provided + return metadata.BinderModelName; + } + else + { + return ModelNames.CreatePropertyModelName(containerName, metadata.PropertyName); + } + } + + private struct PropertyKey + { + public readonly Type ContainerType; + + public readonly string PropertyName; + + public readonly BindingSource Source; + + public PropertyKey(ModelMetadata metadata, BindingSource source) + { + ContainerType = metadata.ContainerType; + PropertyName = metadata.PropertyName; + Source = source; + } + } + + private class PropertyKeyEqualityComparer : IEqualityComparer + { + public bool Equals(PropertyKey x, PropertyKey y) + { + return + x.ContainerType == y.ContainerType && + x.PropertyName == y.PropertyName && + x.Source == y.Source; + } + + public int GetHashCode(PropertyKey obj) + { + var hashCodeCombiner = HashCodeCombiner.Start(); + hashCodeCombiner.Add(obj.ContainerType); + hashCodeCombiner.Add(obj.PropertyName); + hashCodeCombiner.Add(obj.Source); + return hashCodeCombiner.CombinedHash; + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs new file mode 100644 index 0000000000..0d3b74c378 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.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; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class MvcApiExplorerMvcCoreBuilderExtensions + { + public static IMvcCoreBuilder AddApiExplorer(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddApiExplorerServices(builder.Services); + return builder; + } + + // Internal for testing. + internal static void AddApiExplorerServices(IServiceCollection services) + { + services.TryAddSingleton(); + services.TryAddEnumerable( + ServiceDescriptor.Transient()); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/IApiDescriptionGroupCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/IApiDescriptionGroupCollectionProvider.cs new file mode 100644 index 0000000000..5227a62cbe --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/IApiDescriptionGroupCollectionProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Provides access to a collection of . + /// + public interface IApiDescriptionGroupCollectionProvider + { + /// + /// Gets a collection of . + /// + ApiDescriptionGroupCollection ApiDescriptionGroups { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj new file mode 100644 index 0000000000..9ea5be90f4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj @@ -0,0 +1,19 @@ + + + + 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. + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;aspnetcoremvc + + + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..40008c2f22 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.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.Runtime.CompilerServices; + +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionProviderContext))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterDescription))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterRouteInfo))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiRequestFormat))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseFormat))] +[assembly: TypeForwardedTo(typeof(Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType))] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ApiExplorer.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/baseline.netcore.json new file mode 100644 index 0000000000..798fc429d3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/baseline.netcore.json @@ -0,0 +1,993 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.ApiExplorer, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetProperty", + "Parameters": [ + { + "Name": "apiDescription", + "Type": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription" + } + ], + "ReturnType": "T0", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "SetProperty", + "Parameters": [ + { + "Name": "apiDescription", + "Type": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription" + }, + { + "Name": "value", + "Type": "T0" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionGroup", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_GroupName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Items", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "groupName", + "Type": "System.String" + }, + { + "Name": "items", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionGroupCollection", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Items", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Version", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "items", + "Type": "System.Collections.Generic.IReadOnlyList" + }, + { + "Name": "version", + "Type": "System.Int32" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionGroupCollectionProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupCollectionProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ApiDescriptionGroups", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionGroupCollection", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupCollectionProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionDescriptorCollectionProvider", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorCollectionProvider" + }, + { + "Name": "apiDescriptionProviders", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.DefaultApiDescriptionProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Int32", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionProviderContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionProviderContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider", + "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" + } + ], + "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupCollectionProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ApiDescriptionGroups", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionGroupCollection", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcApiExplorerMvcCoreBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddApiExplorer", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptVerbsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptVerbsAttribute.cs new file mode 100644 index 0000000000..3c889c2320 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptVerbsAttribute.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 System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies what HTTP methods an action supports. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public sealed class AcceptVerbsAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider + { + private int? _order; + + /// + /// Initializes a new instance of the class. + /// + /// The HTTP method the action supports. + public AcceptVerbsAttribute(string method) + : this(new [] { method }) + { + if (method == null) + { + throw new ArgumentNullException(nameof(method)); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The HTTP methods the action supports. + public AcceptVerbsAttribute(params string[] methods) + { + HttpMethods = methods.Select(method => method.ToUpperInvariant()); + } + + /// + /// Gets the HTTP methods the action supports. + /// + public IEnumerable HttpMethods { get; } + + /// + /// The route template. May be null. + /// + public string Route { get; set; } + + /// + string IRouteTemplateProvider.Template => Route; + + /// + /// Gets the route order. The order determines the order of route execution. Routes with a lower + /// order value are tried first. When a route doesn't specify a value, it gets the value of the + /// or a default value of 0 if the + /// doesn't define a value on the controller. + /// + public int Order + { + get { return _order ?? 0; } + set { _order = value; } + } + + /// + int? IRouteTemplateProvider.Order => _order; + + /// + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs new file mode 100644 index 0000000000..dd0480c39e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs @@ -0,0 +1,94 @@ +// Copyright (c) .NET 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.Core; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that returns a Accepted (202) response with a Location header. + /// + public class AcceptedAtActionResult : ObjectResult + { + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to format in the entity body. + public AcceptedAtActionResult( + string actionName, + string controllerName, + object routeValues, + object value) + : base(value) + { + ActionName = actionName; + ControllerName = controllerName; + RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); + StatusCode = StatusCodes.Status202Accepted; + } + + /// + /// Gets or sets the used to generate URLs. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + /// Gets or sets the name of the action to use for generating the URL. + /// + public string ActionName { get; set; } + + /// + /// Gets or sets the name of the controller to use for generating the URL. + /// + public string ControllerName { get; set; } + + /// + /// Gets or sets the route data to use for generating the URL. + /// + public RouteValueDictionary RouteValues { get; set; } + + /// + public override void OnFormatting(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + base.OnFormatting(context); + + var request = context.HttpContext.Request; + + var urlHelper = UrlHelper; + if (urlHelper == null) + { + var services = context.HttpContext.RequestServices; + urlHelper = services.GetRequiredService().GetUrlHelper(context); + } + + var url = urlHelper.Action( + ActionName, + ControllerName, + RouteValues, + request.Scheme, + request.Host.ToUriComponent()); + + if (string.IsNullOrEmpty(url)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + context.HttpContext.Response.Headers[HeaderNames.Location] = url; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs new file mode 100644 index 0000000000..bfde81d3ae --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that returns a Accepted (202) response with a Location header. + /// + public class AcceptedAtRouteResult : ObjectResult + { + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The route data to use for generating the URL. + /// The value to format in the entity body. + public AcceptedAtRouteResult(object routeValues, object value) + : this(routeName: null, routeValues: routeValues, value: value) + { + } + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to format in the entity body. + public AcceptedAtRouteResult( + string routeName, + object routeValues, + object value) + : base(value) + { + RouteName = routeName; + RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); + StatusCode = StatusCodes.Status202Accepted; + } + + /// + /// Gets or sets the used to generate URLs. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + /// Gets or sets the name of the route to use for generating the URL. + /// + public string RouteName { get; set; } + + /// + /// Gets or sets the route data to use for generating the URL. + /// + public RouteValueDictionary RouteValues { get; set; } + + /// + public override void OnFormatting(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + base.OnFormatting(context); + + var urlHelper = UrlHelper; + if (urlHelper == null) + { + var services = context.HttpContext.RequestServices; + urlHelper = services.GetRequiredService().GetUrlHelper(context); + } + + var url = urlHelper.Link(RouteName, RouteValues); + + if (string.IsNullOrEmpty(url)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + context.HttpContext.Response.Headers[HeaderNames.Location] = url; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs new file mode 100644 index 0000000000..30b33a4fc5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.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.Http; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that returns an Accepted (202) response with a Location header. + /// + public class AcceptedResult : ObjectResult + { + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + public AcceptedResult() + : base(value: null) + { + StatusCode = StatusCodes.Status202Accepted; + } + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// 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) + : base(value) + { + Location = location; + StatusCode = StatusCodes.Status202Accepted; + } + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// 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) + : base(value) + { + if (locationUri == null) + { + throw new ArgumentNullException(nameof(locationUri)); + } + + if (locationUri.IsAbsoluteUri) + { + Location = locationUri.AbsoluteUri; + } + else + { + Location = locationUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); + } + + StatusCode = StatusCodes.Status202Accepted; + } + + /// + /// Gets or sets the location at which the status of the requested content can be monitored. + /// + public string Location { get; set; } + + /// + public override void OnFormatting(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + base.OnFormatting(context); + + if (!string.IsNullOrEmpty(Location)) + { + context.HttpContext.Response.Headers[HeaderNames.Location] = Location; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionConstraints/ActionMethodSelectorAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionConstraints/ActionMethodSelectorAttribute.cs new file mode 100644 index 0000000000..6cf3d35952 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionConstraints/ActionMethodSelectorAttribute.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; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.ActionConstraints +{ + /// + /// Base class for attributes which can implement conditional logic to enable or disable an action + /// for a given request. See . + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public abstract class ActionMethodSelectorAttribute : Attribute, IActionConstraint + { + /// + public int Order { get; set; } + + /// + public bool Accept(ActionConstraintContext context) + { + return IsValidForRequest(context.RouteContext, context.CurrentCandidate.Action); + } + + /// + /// Determines whether the action selection is valid for the specified route context. + /// + /// The route context. + /// Information about the action. + /// + /// if the action selection is valid for the specified context; + /// otherwise, . + /// + public abstract bool IsValidForRequest(RouteContext routeContext, ActionDescriptor action); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionContextAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionContextAttribute.cs new file mode 100644 index 0000000000..2923ae399b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionContextAttribute.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 System; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies that a controller property should be set with the current + /// when creating the controller. The property must have a public + /// set method. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class ActionContextAttribute : Attribute + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionNameAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionNameAttribute.cs new file mode 100644 index 0000000000..78a374b517 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionNameAttribute.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 System; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies the name of an action. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class ActionNameAttribute : Attribute + { + /// + /// Initializes a new instance. + /// + /// The name of the action. + public ActionNameAttribute(string name) + { + Name = name; + } + + /// + /// Gets the name of the action. + /// + public string Name { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionResult.cs new file mode 100644 index 0000000000..413d47aab6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionResult.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A default implementation of . + /// + public abstract class ActionResult : IActionResult + { + /// + /// Executes the result operation of the action method asynchronously. This method is called by MVC to process + /// the result of an action method. + /// The default implementation of this method calls the method and + /// returns a completed task. + /// + /// The context in which the result is executed. The context information includes + /// information about the action that was executed and request information. + /// A task that represents the asynchronous execute operation. + public virtual Task ExecuteResultAsync(ActionContext context) + { + ExecuteResult(context); + return Task.CompletedTask; + } + + /// + /// Executes the result operation of the action method synchronously. This method is called by MVC to process + /// the result of an action method. + /// + /// The context in which the result is executed. The context information includes + /// information about the action that was executed and request information. + public virtual void ExecuteResult(ActionContext context) + { + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionResultOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionResultOfT.cs new file mode 100644 index 0000000000..f443d78805 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionResultOfT.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 Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A type that wraps either an instance or an . + /// + /// The type of the result. + public sealed class ActionResult : IConvertToActionResult + { + /// + /// Initializes a new instance of using the specified . + /// + /// The value. + public ActionResult(TValue value) + { + Value = value; + } + + /// + /// Intializes a new instance of using the specified . + /// + /// The . + public ActionResult(ActionResult result) + { + Result = result ?? throw new ArgumentNullException(nameof(result)); + } + + /// + /// Gets the . + /// + public ActionResult Result { get; } + + /// + /// Gets the value. + /// + public TValue Value { get; } + + public static implicit operator ActionResult(TValue value) + { + return new ActionResult(value); + } + + public static implicit operator ActionResult(ActionResult result) + { + return new ActionResult(result); + } + + IActionResult IConvertToActionResult.Convert() + { + return Result ?? new ObjectResult(Value) + { + DeclaredType = typeof(TValue), + }; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs new file mode 100644 index 0000000000..e955f25768 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Options used to configure behavior for types annotated with . + /// + public class ApiBehaviorOptions + { + private Func _invalidModelStateResponseFactory; + + /// + /// 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 + { + get => _invalidModelStateResponseFactory; + set => _invalidModelStateResponseFactory = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// Gets or sets a value that determines if the filter that returns an when + /// is invalid is suppressed. . + /// + public bool SuppressModelStateInvalidFilter { get; set; } + + /// + /// Gets or sets a value that determines if model binding sources are inferred for action parameters on controllers annotated + /// with is suppressed. + /// + /// When enabled, the following sources are inferred: + /// Parameters that appear as route values, are assumed to be bound from the path (). + /// Parameters of type and are assumed to be bound from form. + /// Parameters that are complex () are assumed to be bound from the body (). + /// All other parameters are assumed to be bound from the query. + /// + /// + public bool SuppressInferBindingSourcesForParameters { get; set; } + + /// + /// Gets or sets a value that determines if an multipart/form-data consumes action constraint is added to parameters + /// that are bound from form data. + /// + public bool SuppressConsumesConstraintForFormFileParameters { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs new file mode 100644 index 0000000000..0f1f1627bf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.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.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. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public class ApiControllerAttribute : ControllerAttribute, IApiBehaviorMetadata + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionGroupNameProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionGroupNameProvider.cs new file mode 100644 index 0000000000..f9839c80e7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionGroupNameProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Represents group name metadata for an ApiDescription. + /// + public interface IApiDescriptionGroupNameProvider + { + /// + /// The group name for the ApiDescription of the associated action or controller. + /// + string GroupName { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionVisibilityProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionVisibilityProvider.cs new file mode 100644 index 0000000000..439c132c61 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionVisibilityProvider.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.ApiExplorer +{ + /// + /// Represents visibility metadata for an ApiDescription. + /// + public interface IApiDescriptionVisibilityProvider + { + /// + /// If false then no ApiDescription objects will be created for the associated controller + /// or action. + /// + bool IgnoreApi { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestFormatMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestFormatMetadataProvider.cs new file mode 100644 index 0000000000..bf83aad0a8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestFormatMetadataProvider.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; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Provides metadata information about the request format to an IApiDescriptionProvider. + /// + /// + /// An should implement this interface to expose metadata information + /// to an IApiDescriptionProvider. + /// + public interface IApiRequestFormatMetadataProvider + { + /// + /// Gets a filtered list of content types which are supported by the + /// for the and . + /// + /// + /// The content type for which the supported content types are desired, or null if any content + /// type can be used. + /// + /// + /// The for which the supported content types are desired. + /// + /// Content types which are supported by the . + IReadOnlyList GetSupportedContentTypes( + string contentType, + Type objectType); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestMetadataProvider.cs new file mode 100644 index 0000000000..e24ca509f4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestMetadataProvider.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.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Provides a set of possible content types than can be consumed by the action. + /// + public interface IApiRequestMetadataProvider : IFilterMetadata + { + /// + /// Configures a collection of allowed content types which can be consumed by the action. + /// + /// The + void SetContentTypes(MediaTypeCollection contentTypes); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs new file mode 100644 index 0000000000..24b4aece4e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.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; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Provides a return type, status code and a set of possible content types returned by a + /// successful execution of the action. + /// + public interface IApiResponseMetadataProvider : IFilterMetadata + { + /// + /// Gets the optimistic return type of the action. + /// + Type Type { get; } + + /// + /// Gets the HTTP status code of the response. + /// + int StatusCode { get; } + + /// + /// Configures a collection of allowed content types which can be produced by the action. + /// + void SetContentTypes(MediaTypeCollection contentTypes); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseTypeMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseTypeMetadataProvider.cs new file mode 100644 index 0000000000..8b15eb0989 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseTypeMetadataProvider.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; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Provides metadata information about the response format to an IApiDescriptionProvider. + /// + /// + /// An should implement this interface to expose metadata information + /// to an IApiDescriptionProvider. + /// + public interface IApiResponseTypeMetadataProvider + { + /// + /// Gets a filtered list of content types which are supported by the + /// for the and . + /// + /// + /// The content type for which the supported content types are desired, or null if any content + /// type can be used. + /// + /// + /// The for which the supported content types are desired. + /// + /// Content types which are supported by the . + IReadOnlyList GetSupportedContentTypes( + string contentType, + Type objectType); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorerSettingsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorerSettingsAttribute.cs new file mode 100644 index 0000000000..2821ad8d51 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorerSettingsAttribute.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 Microsoft.AspNetCore.Mvc.ApiExplorer; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Controls the visibility and group name for an ApiDescription + /// of the associated controller class or action method. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class ApiExplorerSettingsAttribute : + Attribute, + IApiDescriptionGroupNameProvider, + IApiDescriptionVisibilityProvider + { + /// + public string GroupName { get; set; } + + /// + public bool IgnoreApi { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionModel.cs new file mode 100644 index 0000000000..6114bc227f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionModel.cs @@ -0,0 +1,143 @@ +// Copyright (c) .NET 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.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + [DebuggerDisplay("{DisplayName}")] + public class ActionModel : ICommonModel, IFilterModel, IApiExplorerModel + { + public ActionModel( + MethodInfo actionMethod, + IReadOnlyList attributes) + { + if (actionMethod == null) + { + throw new ArgumentNullException(nameof(actionMethod)); + } + + if (attributes == null) + { + throw new ArgumentNullException(nameof(attributes)); + } + + ActionMethod = actionMethod; + + ApiExplorer = new ApiExplorerModel(); + Attributes = new List(attributes); + Filters = new List(); + Parameters = new List(); + RouteValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + Properties = new Dictionary(); + Selectors = new List(); + } + + public ActionModel(ActionModel other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + ActionMethod = other.ActionMethod; + ActionName = other.ActionName; + + // Not making a deep copy of the controller, this action still belongs to the same controller. + Controller = other.Controller; + + // These are just metadata, safe to create new collections + Attributes = new List(other.Attributes); + Filters = new List(other.Filters); + Properties = new Dictionary(other.Properties); + RouteValues = new Dictionary(other.RouteValues, StringComparer.OrdinalIgnoreCase); + + // Make a deep copy of other 'model' types. + ApiExplorer = new ApiExplorerModel(other.ApiExplorer); + Parameters = new List(other.Parameters.Select(p => new ParameterModel(p) { Action = this })); + Selectors = new List(other.Selectors.Select(s => new SelectorModel(s))); + } + + public MethodInfo ActionMethod { get; } + + public string ActionName { get; set; } + + /// + /// Gets or sets the for this action. + /// + /// + /// allows configuration of settings for ApiExplorer + /// which apply to the action. + /// + /// Settings applied by override settings from + /// and . + /// + public ApiExplorerModel ApiExplorer { get; set; } + + public IReadOnlyList Attributes { get; } + + public ControllerModel Controller { get; set; } + + public IList Filters { get; } + + public IList Parameters { get; } + + /// + /// Gets a collection of route values that must be present in the + /// for the corresponding action to be selected. + /// + /// + /// + /// The value of is considered an implicit route value corresponding + /// to the key action and the value of is + /// considered an implicit route value corresponding to the key controller. These entries + /// will be implicitly added to when the action + /// descriptor is created, but will not be visible in . + /// + /// + /// Entries in can override entries in + /// . + /// + /// + public IDictionary RouteValues { get; } + + /// + /// Gets a set of properties associated with the action. + /// These properties will be copied to . + /// + /// + /// Entries will take precedence over entries with the same key in + /// and . + /// + public IDictionary Properties { get; } + + MemberInfo ICommonModel.MemberInfo => ActionMethod; + + string ICommonModel.Name => ActionName; + + public IList Selectors { get; } + + public string DisplayName + { + get + { + if (Controller == null) + { + return ActionMethod.Name; + } + + var controllerType = TypeNameHelper.GetTypeDisplayName(Controller.ControllerType); + var controllerAssembly = Controller?.ControllerType.Assembly.GetName().Name; + return $"{controllerType}.{ActionMethod.Name} ({controllerAssembly})"; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiExplorerModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiExplorerModel.cs new file mode 100644 index 0000000000..99a6326679 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiExplorerModel.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.ApplicationModels +{ + /// + /// A model for ApiExplorer properties associated with a controller or action. + /// + public class ApiExplorerModel + { + /// + /// Creates a new . + /// + public ApiExplorerModel() + { + } + + /// + /// Creates a new with properties copied from . + /// + /// The to copy. + public ApiExplorerModel(ApiExplorerModel other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + GroupName = other.GroupName; + IsVisible = other.IsVisible; + } + + /// + /// If true, APIExplorer.ApiDescription objects will be created for the associated + /// controller or action. + /// + /// + /// Set this value to configure whether or not the associated controller or action will appear in ApiExplorer. + /// + public bool? IsVisible { get; set; } + + /// + /// The value for APIExplorer.ApiDescription.GroupName of + /// APIExplorer.ApiDescription objects created for the associated controller or action. + /// + public string GroupName { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModel.cs new file mode 100644 index 0000000000..59a1b5fe50 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModel.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.Collections.Generic; +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + [DebuggerDisplay("ApplicationModel: Controllers: {Controllers.Count}, Filters: {Filters.Count}")] + public class ApplicationModel : IPropertyModel, IFilterModel, IApiExplorerModel + { + public ApplicationModel() + { + ApiExplorer = new ApiExplorerModel(); + Controllers = new List(); + Filters = new List(); + Properties = new Dictionary(); + } + + /// + /// Gets or sets the for the application. + /// + /// + /// allows configuration of default settings + /// for ApiExplorer that apply to all actions unless overridden by + /// or . + /// + /// If using to set to + /// true, this setting will only be honored for actions which use attribute routing. + /// + public ApiExplorerModel ApiExplorer { get; set; } + + public IList Controllers { get; } + + public IList Filters { get; } + + /// + /// Gets a set of properties associated with all actions. + /// These properties will be copied to . + /// + public IDictionary Properties { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModelProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModelProviderContext.cs new file mode 100644 index 0000000000..7bc2abd1b0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModelProviderContext.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; +using System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// A context object for . + /// + public class ApplicationModelProviderContext + { + public ApplicationModelProviderContext(IEnumerable controllerTypes) + { + if (controllerTypes == null) + { + throw new ArgumentNullException(nameof(controllerTypes)); + } + + ControllerTypes = controllerTypes; + } + + public IEnumerable ControllerTypes { get; } + + /// + /// Gets the . + /// + public ApplicationModel Result { get; } = new ApplicationModel(); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs new file mode 100644 index 0000000000..07e6a20c48 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs @@ -0,0 +1,423 @@ +// Copyright (c) .NET 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.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class AttributeRouteModel + { + private static readonly AttributeRouteModel _default = new AttributeRouteModel(); + + public AttributeRouteModel() + { + } + + public AttributeRouteModel(IRouteTemplateProvider templateProvider) + { + if (templateProvider == null) + { + throw new ArgumentNullException(nameof(templateProvider)); + } + + Attribute = templateProvider; + Template = templateProvider.Template; + Order = templateProvider.Order; + Name = templateProvider.Name; + } + + public AttributeRouteModel(AttributeRouteModel other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + Attribute = other.Attribute; + Name = other.Name; + Order = other.Order; + Template = other.Template; + SuppressLinkGeneration = other.SuppressLinkGeneration; + SuppressPathMatching = other.SuppressPathMatching; + } + + public IRouteTemplateProvider Attribute { get;} + + public string Template { get; set; } + + public int? Order { get; set; } + + public string Name { get; set; } + + /// + /// Gets or sets a value that determines if this model participates in link generation. + /// + public bool SuppressLinkGeneration { get; set; } + + /// + /// Gets or sets a value that determines if this model participates in path matching (inbound routing). + /// + public bool SuppressPathMatching { get; set; } + + public bool IsAbsoluteTemplate => Template != null && IsOverridePattern(Template); + + /// + /// Combines two instances and returns + /// a new instance with the result. + /// + /// The left . + /// The right . + /// A new instance of that represents the + /// combination of the two instances or null if both + /// parameters are null. + public static AttributeRouteModel CombineAttributeRouteModel( + AttributeRouteModel left, + AttributeRouteModel right) + { + right = right ?? _default; + + // If the right template is an override template (starts with / or ~/) + // we ignore the values from left. + if (left == null || IsOverridePattern(right.Template)) + { + left = _default; + } + + var combinedTemplate = CombineTemplates(left.Template, right.Template); + + // The action is not attribute routed. + if (combinedTemplate == null) + { + return null; + } + + return new AttributeRouteModel() + { + Template = combinedTemplate, + Order = right.Order ?? left.Order, + Name = ChooseName(left, right), + SuppressLinkGeneration = left.SuppressLinkGeneration || right.SuppressLinkGeneration, + SuppressPathMatching = left.SuppressPathMatching || right.SuppressPathMatching, + }; + } + + /// + /// Combines the prefix and route template for an attribute route. + /// + /// The prefix. + /// The route template. + /// The combined pattern. + public static string CombineTemplates(string prefix, string template) + { + var result = CombineCore(prefix, template); + return CleanTemplate(result); + } + + /// + /// Determines if a template pattern can be used to override a prefix. + /// + /// The template. + /// true if this is an overriding template, false otherwise. + /// + /// Route templates starting with "~/" or "/" can be used to override the prefix. + /// + public static bool IsOverridePattern(string template) + { + return template != null && + (template.StartsWith("~/", StringComparison.Ordinal) || + template.StartsWith("/", StringComparison.Ordinal)); + } + + private static string ChooseName( + AttributeRouteModel left, + AttributeRouteModel right) + { + if (right.Name == null && string.IsNullOrEmpty(right.Template)) + { + return left.Name; + } + else + { + return right.Name; + } + } + + private static string CombineCore(string left, string right) + { + if (left == null && right == null) + { + return null; + } + else if (right == null) + { + return left; + } + else if (IsEmptyLeftSegment(left) || IsOverridePattern(right)) + { + return right; + } + + if (left.EndsWith("/", StringComparison.Ordinal)) + { + return left + right; + } + + // Both templates contain some text. + return left + "/" + right; + } + + private static bool IsEmptyLeftSegment(string template) + { + return template == null || + template.Equals(string.Empty, StringComparison.Ordinal) || + template.Equals("~/", StringComparison.Ordinal) || + template.Equals("/", StringComparison.Ordinal); + } + + private static string CleanTemplate(string result) + { + if (result == null) + { + return null; + } + + // This is an invalid combined template, so we don't want to + // accidentally clean it and produce a valid template. For that + // reason we ignore the clean up process for it. + if (result.Equals("//", StringComparison.Ordinal)) + { + return result; + } + + var startIndex = 0; + if (result.StartsWith("/", StringComparison.Ordinal)) + { + startIndex = 1; + } + else if (result.StartsWith("~/", StringComparison.Ordinal)) + { + startIndex = 2; + } + + // We are in the case where the string is "/" or "~/" + if (startIndex == result.Length) + { + return string.Empty; + } + + var subStringLength = result.Length - startIndex; + if (result.EndsWith("/", StringComparison.Ordinal)) + { + subStringLength--; + } + + return result.Substring(startIndex, subStringLength); + } + + public static string ReplaceTokens(string template, IDictionary values) + { + var builder = new StringBuilder(); + var state = TemplateParserState.Plaintext; + + int? tokenStart = null; + + // We'll run the loop one extra time with 'null' to detect the end of the string. + for (var i = 0; i <= template.Length; i++) + { + var c = i < template.Length ? (char?)template[i] : null; + switch (state) + { + case TemplateParserState.Plaintext: + if (c == '[') + { + state = TemplateParserState.SeenLeft; + break; + } + else if (c == ']') + { + state = TemplateParserState.SeenRight; + break; + } + else if (c == null) + { + // We're at the end of the string, nothing left to do. + break; + } + else + { + builder.Append(c); + break; + } + case TemplateParserState.SeenLeft: + if (c == '[') + { + // This is an escaped left-bracket + builder.Append(c); + state = TemplateParserState.Plaintext; + break; + } + else if (c == ']') + { + // This is zero-width parameter - not allowed. + var message = Resources.FormatAttributeRoute_TokenReplacement_InvalidSyntax( + template, + Resources.AttributeRoute_TokenReplacement_EmptyTokenNotAllowed); + throw new InvalidOperationException(message); + } + else if (c == null) + { + // This is a left-bracket at the end of the string. + var message = Resources.FormatAttributeRoute_TokenReplacement_InvalidSyntax( + template, + Resources.AttributeRoute_TokenReplacement_UnclosedToken); + throw new InvalidOperationException(message); + } + else + { + tokenStart = i; + state = TemplateParserState.InsideToken; + break; + } + case TemplateParserState.SeenRight: + if (c == ']') + { + // This is an escaped right-bracket + builder.Append(c); + state = TemplateParserState.Plaintext; + break; + } + else if (c == null) + { + // This is an imbalanced right-bracket at the end of the string. + var message = Resources.FormatAttributeRoute_TokenReplacement_InvalidSyntax( + template, + Resources.AttributeRoute_TokenReplacement_ImbalancedSquareBrackets); + throw new InvalidOperationException(message); + } + else + { + // This is an imbalanced right-bracket. + var message = Resources.FormatAttributeRoute_TokenReplacement_InvalidSyntax( + template, + Resources.AttributeRoute_TokenReplacement_ImbalancedSquareBrackets); + throw new InvalidOperationException(message); + } + case TemplateParserState.InsideToken: + if (c == '[') + { + state = TemplateParserState.InsideToken | TemplateParserState.SeenLeft; + break; + } + else if (c == ']') + { + state = TemplateParserState.InsideToken | TemplateParserState.SeenRight; + break; + } + else if (c == null) + { + // This is an unclosed replacement token + var message = Resources.FormatAttributeRoute_TokenReplacement_InvalidSyntax( + template, + Resources.AttributeRoute_TokenReplacement_UnclosedToken); + throw new InvalidOperationException(message); + } + else + { + // This is a just part of the parameter + break; + } + case TemplateParserState.InsideToken | TemplateParserState.SeenLeft: + if (c == '[') + { + // This is an escaped left-bracket + state = TemplateParserState.InsideToken; + break; + } + else + { + // Unescaped left-bracket is not allowed inside a token. + var message = Resources.FormatAttributeRoute_TokenReplacement_InvalidSyntax( + template, + Resources.AttributeRoute_TokenReplacement_UnescapedBraceInToken); + throw new InvalidOperationException(message); + } + case TemplateParserState.InsideToken | TemplateParserState.SeenRight: + if (c == ']') + { + // This is an escaped right-bracket + state = TemplateParserState.InsideToken; + break; + } + else + { + // This is the end of a replacement token. + var token = template + .Substring(tokenStart.Value, i - tokenStart.Value - 1) + .Replace("[[", "[") + .Replace("]]", "]"); + + if (!values.TryGetValue(token, out var value)) + { + // Value not found + var message = Resources.FormatAttributeRoute_TokenReplacement_ReplacementValueNotFound( + template, + token, + string.Join(", ", values.Keys.OrderBy(k => k, StringComparer.OrdinalIgnoreCase))); + throw new InvalidOperationException(message); + } + + builder.Append(value); + + if (c == '[') + { + state = TemplateParserState.SeenLeft; + } + else if (c == ']') + { + state = TemplateParserState.SeenRight; + } + else if (c == null) + { + state = TemplateParserState.Plaintext; + } + else + { + builder.Append(c); + state = TemplateParserState.Plaintext; + } + + tokenStart = null; + break; + } + } + } + + return builder.ToString(); + } + + [Flags] + private enum TemplateParserState : uint + { + // default state - allow non-special characters to pass through to the + // buffer. + Plaintext = 0, + + // We're inside a replacement token, may be combined with other states to detect + // a possible escaped bracket inside the token. + InsideToken = 1, + + // We've seen a left brace, need to see the next character to find out if it's escaped + // or not. + SeenLeft = 2, + + // We've seen a right brace, need to see the next character to find out if it's escaped + // or not. + SeenRight = 4, + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ControllerModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ControllerModel.cs new file mode 100644 index 0000000000..cd26b0e26d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ControllerModel.cs @@ -0,0 +1,133 @@ +// Copyright (c) .NET 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.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + [DebuggerDisplay("{DisplayName}")] + public class ControllerModel : ICommonModel, IFilterModel, IApiExplorerModel + { + public ControllerModel( + TypeInfo controllerType, + IReadOnlyList attributes) + { + if (controllerType == null) + { + throw new ArgumentNullException(nameof(controllerType)); + } + + if (attributes == null) + { + throw new ArgumentNullException(nameof(attributes)); + } + + ControllerType = controllerType; + + Actions = new List(); + ApiExplorer = new ApiExplorerModel(); + Attributes = new List(attributes); + ControllerProperties = new List(); + Filters = new List(); + Properties = new Dictionary(); + RouteValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + Selectors = new List(); + } + + public ControllerModel(ControllerModel other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + ControllerName = other.ControllerName; + ControllerType = other.ControllerType; + + // Still part of the same application + Application = other.Application; + + // These are just metadata, safe to create new collections + Attributes = new List(other.Attributes); + Filters = new List(other.Filters); + RouteValues = new Dictionary(other.RouteValues, StringComparer.OrdinalIgnoreCase); + Properties = new Dictionary(other.Properties); + + // Make a deep copy of other 'model' types. + Actions = new List(other.Actions.Select(a => new ActionModel(a) { Controller = this })); + ApiExplorer = new ApiExplorerModel(other.ApiExplorer); + ControllerProperties = + new List(other.ControllerProperties.Select(p => new PropertyModel(p) { Controller = this })); + Selectors = new List(other.Selectors.Select(s => new SelectorModel(s))); + } + + public IList Actions { get; } + + /// + /// Gets or sets the for this controller. + /// + /// + /// allows configuration of settings for ApiExplorer + /// which apply to all actions in the controller unless overridden by . + /// + /// Settings applied by override settings from + /// . + /// + public ApiExplorerModel ApiExplorer { get; set; } + + public ApplicationModel Application { get; set; } + + public IReadOnlyList Attributes { get; } + + MemberInfo ICommonModel.MemberInfo => ControllerType; + + string ICommonModel.Name => ControllerName; + + public string ControllerName { get; set; } + + public TypeInfo ControllerType { get; } + + public IList ControllerProperties { get; } + + public IList Filters { get; } + + /// + /// Gets a collection of route values that must be present in the + /// for the corresponding action to be selected. + /// + /// + /// Entries in can be overridden by entries in + /// . + /// + public IDictionary RouteValues { get; } + + /// + /// Gets a set of properties associated with the controller. + /// These properties will be copied to . + /// + /// + /// Entries will take precedence over entries with the same key + /// in . + /// + public IDictionary Properties { get; } + + public IList Selectors { get; } + + public string DisplayName + { + get + { + var controllerType = TypeNameHelper.GetTypeDisplayName(ControllerType); + var controllerAssembly = ControllerType.Assembly.GetName().Name; + return $"{controllerType} ({controllerAssembly})"; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IActionModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IActionModelConvention.cs new file mode 100644 index 0000000000..6d235666f9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IActionModelConvention.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. + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// Allows customization of the . + /// + /// + /// To use this interface, create an class which implements the interface and + /// place it on an action method. + /// + /// customizations run after + /// customizations and before + /// customizations. + /// + public interface IActionModelConvention + { + /// + /// Called to apply the convention to the . + /// + /// The . + void Apply(ActionModel action); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApiExplorerModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApiExplorerModel.cs new file mode 100644 index 0000000000..c5e36ac038 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApiExplorerModel.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 Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public interface IApiExplorerModel + { + ApiExplorerModel ApiExplorer { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelConvention.cs new file mode 100644 index 0000000000..7388b7f5e4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelConvention.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. + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// Allows customization of the . + /// + /// + /// Implementations of this interface can be registered in + /// to customize metadata about the application. + /// + /// run before other types of customizations to the + /// reflected model. + /// + public interface IApplicationModelConvention + { + /// + /// Called to apply the convention to the . + /// + /// The . + void Apply(ApplicationModel application); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelProvider.cs new file mode 100644 index 0000000000..8d8814ad43 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// Builds or modifies an for action discovery. + /// + public interface IApplicationModelProvider + { + /// + /// Gets the order value for determining the order of execution of providers. Providers execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Providers are executed in an ordering determined by an ascending sort of the property. + /// A provider with a lower numeric value of will have its + /// called before that of a provider with a higher numeric value of + /// . The method is called in the reverse ordering after + /// all calls to . A provider with a lower numeric value of + /// will have its method called after that of a provider + /// with a higher numeric value of . + /// + /// + /// If two providers have the same numeric value of , then their relative execution order + /// is undefined. + /// + /// + int Order { get; } + + /// + /// Executed for the first pass of building. See . + /// + /// The . + void OnProvidersExecuting(ApplicationModelProviderContext context); + + /// + /// Executed for the second pass of building. See . + /// + /// The . + void OnProvidersExecuted(ApplicationModelProviderContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IBindingModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IBindingModel.cs new file mode 100644 index 0000000000..e57ff5589c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IBindingModel.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.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public interface IBindingModel + { + BindingInfo BindingInfo { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ICommonModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ICommonModel.cs new file mode 100644 index 0000000000..0fc83aca3e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ICommonModel.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.Collections.Generic; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public interface ICommonModel : IPropertyModel + { + IReadOnlyList Attributes { get; } + MemberInfo MemberInfo { get; } + string Name { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IControllerModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IControllerModelConvention.cs new file mode 100644 index 0000000000..37d08ec440 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IControllerModelConvention.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. + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// Allows customization of the . + /// + /// + /// To use this interface, create an class which implements the interface and + /// place it on a controller class. + /// + /// customizations run after + /// customizations and before + /// customizations. + /// + public interface IControllerModelConvention + { + /// + /// Called to apply the convention to the . + /// + /// The . + void Apply(ControllerModel controller); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IFilterModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IFilterModel.cs new file mode 100644 index 0000000000..98f89df0a0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IFilterModel.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 System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public interface IFilterModel + { + IList Filters { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelBaseConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelBaseConvention.cs new file mode 100644 index 0000000000..2457b89f95 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelBaseConvention.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. + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// Allows customization of the properties and parameters on controllers and Razor Pages. + /// + /// + /// To use this interface, create an class which implements the interface and + /// place it on an action method parameter. + /// + public interface IParameterModelBaseConvention + { + /// + /// Called to apply the convention to the . + /// + /// The . + void Apply(ParameterModelBase parameter); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelConvention.cs new file mode 100644 index 0000000000..eddbbe383c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelConvention.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.ApplicationModels +{ + /// + /// Allows customization of the . + /// + /// + /// To use this interface, create an class which implements the interface and + /// place it on an action method parameter. + /// + /// customizations run after + /// customizations. + /// + public interface IParameterModelConvention + { + void Apply(ParameterModel parameter); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IPropertyModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IPropertyModel.cs new file mode 100644 index 0000000000..5fbdb4567a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IPropertyModel.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.ApplicationModels +{ + public interface IPropertyModel + { + IDictionary Properties { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModel.cs new file mode 100644 index 0000000000..f473ab94f4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModel.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET 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.Reflection; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + [DebuggerDisplay("ParameterModel: Name={ParameterName}")] + public class ParameterModel : ParameterModelBase, ICommonModel + { + public ParameterModel( + ParameterInfo parameterInfo, + IReadOnlyList attributes) + : base(parameterInfo?.ParameterType, attributes) + { + ParameterInfo = parameterInfo ?? throw new ArgumentNullException(nameof(parameterInfo)); + } + + public ParameterModel(ParameterModel other) + : base(other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + Action = other.Action; + ParameterInfo = other.ParameterInfo; + } + + public ActionModel Action { get; set; } + + public new IDictionary Properties => base.Properties; + + public new IReadOnlyList Attributes => base.Attributes; + + MemberInfo ICommonModel.MemberInfo => ParameterInfo.Member; + + public ParameterInfo ParameterInfo { get; } + + public string ParameterName + { + get => Name; + set => Name = value; + } + + public string DisplayName + { + get + { + var parameterTypeName = TypeNameHelper.GetTypeDisplayName(ParameterInfo.ParameterType, fullName: false); + return $"{parameterTypeName} {ParameterName}"; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModelBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModelBase.cs new file mode 100644 index 0000000000..7937d8b3c7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModelBase.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 System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// A model type for reading and manipulation properties and parameters. + /// + /// Derived instances of this type represent properties and parameters for controllers, and Razor Pages. + /// + /// + public abstract class ParameterModelBase : IBindingModel + { + protected ParameterModelBase( + Type parameterType, + IReadOnlyList attributes) + { + ParameterType = parameterType ?? throw new ArgumentNullException(nameof(parameterType)); + Attributes = new List(attributes ?? throw new ArgumentNullException(nameof(attributes))); + + Properties = new Dictionary(); + } + + protected ParameterModelBase(ParameterModelBase other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + ParameterType = other.ParameterType; + Attributes = new List(other.Attributes); + BindingInfo = other.BindingInfo == null ? null : new BindingInfo(other.BindingInfo); + Name = other.Name; + Properties = new Dictionary(other.Properties); + } + + public IReadOnlyList Attributes { get; } + + public IDictionary Properties { get; } + + public Type ParameterType { get; } + + public string Name { get; protected set; } + + public BindingInfo BindingInfo { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/PropertyModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/PropertyModel.cs new file mode 100644 index 0000000000..8f26d0648a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/PropertyModel.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.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// A type which is used to represent a property in a . + /// + [DebuggerDisplay("PropertyModel: Name={PropertyName}")] + public class PropertyModel : ParameterModelBase, ICommonModel, IBindingModel + { + /// + /// Creates a new instance of . + /// + /// The for the underlying property. + /// Any attributes which are annotated on the property. + public PropertyModel( + PropertyInfo propertyInfo, + IReadOnlyList attributes) + : base(propertyInfo?.PropertyType, attributes) + { + PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo)); + } + + /// + /// Creates a new instance of from a given . + /// + /// The which needs to be copied. + public PropertyModel(PropertyModel other) + : base(other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + Controller = other.Controller; + BindingInfo = BindingInfo == null ? null : new BindingInfo(other.BindingInfo); + PropertyInfo = other.PropertyInfo; + } + + /// + /// Gets or sets the this is associated with. + /// + public ControllerModel Controller { get; set; } + + MemberInfo ICommonModel.MemberInfo => PropertyInfo; + + public new IDictionary Properties => base.Properties; + + public new IReadOnlyList Attributes => base.Attributes; + + public PropertyInfo PropertyInfo { get; } + + public string PropertyName + { + get => Name; + set => Name = value; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/SelectorModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/SelectorModel.cs new file mode 100644 index 0000000000..e838e17c04 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/SelectorModel.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 System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ActionConstraints; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class SelectorModel + { + public SelectorModel() + { + ActionConstraints = new List(); + } + + public SelectorModel(SelectorModel other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + ActionConstraints = new List(other.ActionConstraints); + + if (other.AttributeRouteModel != null) + { + AttributeRouteModel = new AttributeRouteModel(other.AttributeRouteModel); + } + } + + public AttributeRouteModel AttributeRouteModel { get; set; } + + public IList ActionConstraints { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationAssembliesProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationAssembliesProvider.cs new file mode 100644 index 0000000000..08149805ff --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationAssembliesProvider.cs @@ -0,0 +1,295 @@ +// Copyright (c) .NET 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.Mvc.Core; +using Microsoft.Extensions.DependencyModel; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + internal class ApplicationAssembliesProvider + { + internal static HashSet ReferenceAssemblies { get; } = new HashSet(StringComparer.OrdinalIgnoreCase) + { + // The deps file for the Microsoft.AspNetCore.All shared runtime is authored in a way where it does not say + // it depends on Microsoft.AspNetCore.Mvc even though it does. Explicitly list it so that referencing this runtime causes + // assembly discovery to work correctly. + "Microsoft.AspNetCore.All", + "Microsoft.AspNetCore.Mvc", + "Microsoft.AspNetCore.Mvc.Abstractions", + "Microsoft.AspNetCore.Mvc.ApiExplorer", + "Microsoft.AspNetCore.Mvc.Core", + "Microsoft.AspNetCore.Mvc.Cors", + "Microsoft.AspNetCore.Mvc.DataAnnotations", + "Microsoft.AspNetCore.Mvc.Formatters.Json", + "Microsoft.AspNetCore.Mvc.Formatters.Xml", + "Microsoft.AspNetCore.Mvc.Localization", + "Microsoft.AspNetCore.Mvc.Razor", + "Microsoft.AspNetCore.Mvc.Razor.Extensions", + "Microsoft.AspNetCore.Mvc.RazorPages", + "Microsoft.AspNetCore.Mvc.TagHelpers", + "Microsoft.AspNetCore.Mvc.ViewFeatures", + }; + + /// + /// Returns an ordered list of application assemblies. + /// + /// The order is as follows: + /// * Entry assembly + /// * Assemblies specified in the application's deps file ordered by name. + /// + /// Each assembly is immediately followed by assemblies specified by annotated ordered by name. + /// + /// + /// + public IEnumerable ResolveAssemblies(Assembly entryAssembly) + { + var dependencyContext = LoadDependencyContext(entryAssembly); + + IEnumerable assemblyItems; + + if (dependencyContext == null || dependencyContext.CompileLibraries.Count == 0) + { + // If an application was built with PreserveCompilationContext = false, CompileLibraries will be empty and we + // can no longer reliably infer the dependency closure. In this case, treat it the same as a missing + // deps file. + assemblyItems = new[] { GetAssemblyItem(entryAssembly) }; + } + else + { + assemblyItems = ResolveFromDependencyContext(dependencyContext); + } + + assemblyItems = assemblyItems + .OrderBy(item => item.Assembly == entryAssembly ? 0 : 1) + .ThenBy(item => item.Assembly.FullName, StringComparer.Ordinal); + + foreach (var item in assemblyItems) + { + yield return item.Assembly; + + foreach (var associatedAssembly in item.RelatedAssemblies.Distinct().OrderBy(assembly => assembly.FullName, StringComparer.Ordinal)) + { + yield return associatedAssembly; + } + } + } + + protected virtual DependencyContext LoadDependencyContext(Assembly assembly) => DependencyContext.Load(assembly); + + private List ResolveFromDependencyContext(DependencyContext dependencyContext) + { + var assemblyItems = new List(); + var relatedAssemblies = new Dictionary(); + + var candidateAssemblies = GetCandidateLibraries(dependencyContext) + .SelectMany(library => GetLibraryAssemblies(dependencyContext, library)); + + foreach (var assembly in candidateAssemblies) + { + var assemblyItem = GetAssemblyItem(assembly); + assemblyItems.Add(assemblyItem); + + foreach (var relatedAssembly in assemblyItem.RelatedAssemblies) + { + if (relatedAssemblies.TryGetValue(relatedAssembly, out var otherEntry)) + { + var message = string.Join( + Environment.NewLine, + Resources.FormatApplicationAssembliesProvider_DuplicateRelatedAssembly(relatedAssembly.FullName), + otherEntry.Assembly.FullName, + assembly.FullName); + + throw new InvalidOperationException(message); + } + + relatedAssemblies.Add(relatedAssembly, assemblyItem); + } + } + + // Remove any top level assemblies that appear as an associated assembly. + assemblyItems.RemoveAll(item => relatedAssemblies.ContainsKey(item.Assembly)); + + return assemblyItems; + } + + protected virtual IEnumerable GetLibraryAssemblies(DependencyContext dependencyContext, RuntimeLibrary runtimeLibrary) + { + foreach (var assemblyName in runtimeLibrary.GetDefaultAssemblyNames(dependencyContext)) + { + var assembly = Assembly.Load(assemblyName); + yield return assembly; + } + } + + protected virtual IReadOnlyList GetRelatedAssemblies(Assembly assembly) + { + // Do not require related assemblies to be available in the default code path. + return RelatedAssemblyAttribute.GetRelatedAssemblies(assembly, throwOnError: false); + } + + private AssemblyItem GetAssemblyItem(Assembly assembly) + { + var relatedAssemblies = GetRelatedAssemblies(assembly); + + // Ensure we don't have any cycles. A cycle could be formed if a related assembly points to the primary assembly. + foreach (var relatedAssembly in relatedAssemblies) + { + if (relatedAssembly.IsDefined(typeof(RelatedAssemblyAttribute))) + { + throw new InvalidOperationException( + Resources.FormatApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional(relatedAssembly.FullName, assembly.FullName)); + } + } + + return new AssemblyItem(assembly, relatedAssemblies); + } + + // Returns a list of libraries that references the assemblies in . + // By default it returns all assemblies that reference any of the primary MVC assemblies + // while ignoring MVC assemblies. + // Internal for unit testing + internal static IEnumerable GetCandidateLibraries(DependencyContext dependencyContext) + { + // When using Microsoft.AspNetCore.App \ Microsoft.AspNetCore.All shared runtimes, entries in the RuntimeLibraries + // get "erased" and it is no longer accurate to query to determine a library's dependency closure. + // We'll use CompileLibraries to calculate the dependency graph and runtime library to resolve assemblies to inspect. + var candidatesResolver = new CandidateResolver(dependencyContext.CompileLibraries, ReferenceAssemblies); + foreach (var library in dependencyContext.RuntimeLibraries) + { + if (candidatesResolver.IsCandidate(library)) + { + yield return library; + } + } + } + + private class CandidateResolver + { + private readonly IDictionary _compileLibraries; + + public CandidateResolver(IReadOnlyList compileLibraries, ISet referenceAssemblies) + { + var compileLibrariesWithNoDuplicates = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var library in compileLibraries) + { + if (compileLibrariesWithNoDuplicates.ContainsKey(library.Name)) + { + throw new InvalidOperationException(Resources.FormatCandidateResolver_DifferentCasedReference(library.Name)); + } + + compileLibrariesWithNoDuplicates.Add(library.Name, CreateDependency(library, referenceAssemblies)); + } + + _compileLibraries = compileLibrariesWithNoDuplicates; + } + + public bool IsCandidate(Library library) + { + var classification = ComputeClassification(library.Name); + return classification == DependencyClassification.ReferencesMvc; + } + + private Dependency CreateDependency(Library library, ISet referenceAssemblies) + { + var classification = DependencyClassification.Unknown; + if (referenceAssemblies.Contains(library.Name)) + { + classification = DependencyClassification.MvcReference; + } + + return new Dependency(library, classification); + } + + private DependencyClassification ComputeClassification(string dependency) + { + if (!_compileLibraries.ContainsKey(dependency)) + { + // Library does not have runtime dependency. Since we can't infer + // anything about it's references, we'll assume it does not have a reference to Mvc. + return DependencyClassification.DoesNotReferenceMvc; + } + + var candidateEntry = _compileLibraries[dependency]; + if (candidateEntry.Classification != DependencyClassification.Unknown) + { + return candidateEntry.Classification; + } + else + { + var classification = DependencyClassification.DoesNotReferenceMvc; + foreach (var candidateDependency in candidateEntry.Library.Dependencies) + { + var dependencyClassification = ComputeClassification(candidateDependency.Name); + if (dependencyClassification == DependencyClassification.ReferencesMvc || + dependencyClassification == DependencyClassification.MvcReference) + { + classification = DependencyClassification.ReferencesMvc; + break; + } + } + + candidateEntry.Classification = classification; + + return classification; + } + } + + private class Dependency + { + public Dependency(Library library, DependencyClassification classification) + { + Library = library; + Classification = classification; + } + + public Library Library { get; } + + public DependencyClassification Classification { get; set; } + + public override string ToString() + { + return $"Library: {Library.Name}, Classification: {Classification}"; + } + } + + private enum DependencyClassification + { + Unknown = 0, + + /// + /// References (directly or transitively) one of the Mvc packages listed in + /// . + /// + ReferencesMvc = 1, + + /// + /// Does not reference (directly or transitively) one of the Mvc packages listed by + /// . + /// + DoesNotReferenceMvc = 2, + + /// + /// One of the references listed in . + /// + MvcReference = 3, + } + } + + private readonly struct AssemblyItem + { + public AssemblyItem(Assembly assembly, IReadOnlyList associatedAssemblies) + { + Assembly = assembly; + RelatedAssemblies = associatedAssemblies; + } + + public Assembly Assembly { get; } + + public IReadOnlyList RelatedAssemblies { get; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPart.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPart.cs new file mode 100644 index 0000000000..807af25a9d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPart.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. + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// A part of an MVC application. + /// + public abstract class ApplicationPart + { + /// + /// Gets the name. + /// + public abstract string Name { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartFactory.cs new file mode 100644 index 0000000000..71dc6a0b41 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartFactory.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 System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Core; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// Specifies a contract for synthesizing one or more instances + /// from an . + /// + /// By default, Mvc registers each application assembly that it discovers as an . + /// Assemblies can optionally specify an to configure parts for the assembly + /// by using . + /// + /// + public abstract class ApplicationPartFactory + { + /// + /// Gets one or more instances for the specified . + /// + /// The . + public abstract IEnumerable GetApplicationParts(Assembly assembly); + + /// + /// Gets the for the specified assembly. + /// + /// An assembly may specify an using . + /// Otherwise, is used. + /// + /// + /// The . + /// An instance of . + public static ApplicationPartFactory GetApplicationPartFactory(Assembly assembly) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + var provideAttribute = assembly.GetCustomAttribute(); + if (provideAttribute == null) + { + return DefaultApplicationPartFactory.Instance; + } + + var type = provideAttribute.GetFactoryType(); + if (!typeof(ApplicationPartFactory).IsAssignableFrom(type)) + { + throw new InvalidOperationException(Resources.FormatApplicationPartFactory_InvalidFactoryType( + type, + nameof(ProvideApplicationPartFactoryAttribute), + typeof(ApplicationPartFactory))); + } + + return (ApplicationPartFactory)Activator.CreateInstance(type); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs new file mode 100644 index 0000000000..1636c61df7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.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; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// Manages the parts and features of an MVC application. + /// + public class ApplicationPartManager + { + /// + /// Gets the list of s. + /// + public IList FeatureProviders { get; } = + new List(); + + /// + /// 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. + /// An may choose to use this an interface as a way to resolve conflicts when + /// multiple instances resolve equivalent feature values. + /// + /// + public IList ApplicationParts { get; } = new List(); + + /// + /// Populates the given using the list of + /// s configured on the + /// . + /// + /// The type of the feature. + /// The feature instance to populate. + public void PopulateFeature(TFeature feature) + { + if (feature == null) + { + throw new ArgumentNullException(nameof(feature)); + } + + foreach (var provider in FeatureProviders.OfType>()) + { + provider.PopulateFeature(ApplicationParts, feature); + } + } + + internal void PopulateDefaultParts(string entryAssemblyName) + { + var entryAssembly = Assembly.Load(new AssemblyName(entryAssemblyName)); + var assembliesProvider = new ApplicationAssembliesProvider(); + var applicationAssemblies = assembliesProvider.ResolveAssemblies(entryAssembly); + + foreach (var assembly in applicationAssemblies) + { + var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly); + foreach (var part in partFactory.GetApplicationParts(assembly)) + { + ApplicationParts.Add(part); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs new file mode 100644 index 0000000000..e3df2d6a14 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.DependencyModel; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// An backed by an . + /// + public class AssemblyPart : + ApplicationPart, + IApplicationPartTypeProvider, + ICompilationReferencesProvider + { + /// + /// Initializes a new instance. + /// + /// The backing . + public AssemblyPart(Assembly assembly) + { + Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); + } + + /// + /// Gets the of the . + /// + public Assembly Assembly { get; } + + /// + /// Gets the name of the . + /// + public override string Name => Assembly.GetName().Name; + + /// + public IEnumerable Types => Assembly.DefinedTypes; + + /// + public IEnumerable GetReferencePaths() + { + if (Assembly.IsDynamic) + { + // Skip loading process for dynamic assemblies. This prevents DependencyContextLoader from reading the + // .deps.json file from either manifest resources or the assembly location, which will fail. + return Enumerable.Empty(); + } + + var dependencyContext = DependencyContext.Load(Assembly); + if (dependencyContext != null) + { + return dependencyContext.CompileLibraries.SelectMany(library => library.ResolveReferencePaths()); + } + + // If an application has been compiled without preserveCompilationContext, return the path to the assembly + // as a reference. For runtime compilation, this will allow the compilation to succeed as long as it least + // one application part has been compiled with preserveCompilationContext and contains a super set of types + // required for the compilation to succeed. + return new[] { Assembly.Location }; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/DefaultApplicationPartFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/DefaultApplicationPartFactory.cs new file mode 100644 index 0000000000..60a08f7974 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/DefaultApplicationPartFactory.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 System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// Default . + /// + public class DefaultApplicationPartFactory : ApplicationPartFactory + { + /// + /// Gets an instance of . + /// + public static DefaultApplicationPartFactory Instance { get; } = new DefaultApplicationPartFactory(); + + /// + /// Gets the sequence of instances that are created by this instance of . + /// + /// Applications may use this method to get the same behavior as this factory produces during MVC's default part discovery. + /// + /// + /// The . + /// The sequence of instances. + public static IEnumerable GetDefaultApplicationParts(Assembly assembly) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + yield return new AssemblyPart(assembly); + } + + /// + public override IEnumerable GetApplicationParts(Assembly assembly) + { + return GetDefaultApplicationParts(assembly); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProvider.cs new file mode 100644 index 0000000000..2ee9ece9f0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProvider.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.ApplicationParts +{ + /// + /// Marker interface for + /// implementations. + /// + public interface IApplicationFeatureProvider + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProviderOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProviderOfT.cs new file mode 100644 index 0000000000..cf7f870bca --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProviderOfT.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// A provider for a given feature. + /// + /// The type of the feature. + public interface IApplicationFeatureProvider : IApplicationFeatureProvider + { + /// + /// Updates the instance. + /// + /// The list of instances in the application. + /// + /// The feature instance to populate. + /// + /// instances in appear in the same ordered sequence they + /// are stored in . This ordering may be used by the feature + /// provider to make precedence decisions. + /// + void PopulateFeature(IEnumerable parts, TFeature feature); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationPartTypeProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationPartTypeProvider.cs new file mode 100644 index 0000000000..616c9a9aea --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationPartTypeProvider.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.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// Exposes a set of types from an . + /// + public interface IApplicationPartTypeProvider + { + /// + /// Gets the list of available types in the . + /// + IEnumerable Types { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ICompilationReferencesProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ICompilationReferencesProvider.cs new file mode 100644 index 0000000000..bb2691bedd --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ICompilationReferencesProvider.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// Exposes one or more reference paths from an . + /// + public interface ICompilationReferencesProvider + { + /// + /// Gets reference paths used to perform runtime compilation. + /// + IEnumerable GetReferencePaths(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/NullApplicationPartFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/NullApplicationPartFactory.cs new file mode 100644 index 0000000000..6fe7ff39ff --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/NullApplicationPartFactory.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.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// An that produces no parts. + /// + /// This factory may be used to to preempt Mvc's default part discovery allowing for custom configuration at a later stage. + /// + /// + public class NullApplicationPartFactory : ApplicationPartFactory + { + /// + public override IEnumerable GetApplicationParts(Assembly assembly) + { + return Enumerable.Empty(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ProvideApplicationPartFactoryAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ProvideApplicationPartFactoryAttribute.cs new file mode 100644 index 0000000000..1ad9e57879 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ProvideApplicationPartFactoryAttribute.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 Microsoft.AspNetCore.Mvc.Core; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// Provides a type. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = false)] + public sealed class ProvideApplicationPartFactoryAttribute : Attribute + { + private readonly Type _applicationPartFactoryType; + private readonly string _applicationPartFactoryTypeName; + + /// + /// Creates a new instance of with the specified type. + /// + /// The factory type. + public ProvideApplicationPartFactoryAttribute(Type factoryType) + { + _applicationPartFactoryType = factoryType ?? throw new ArgumentNullException(nameof(factoryType)); + } + + /// + /// Creates a new instance of with the specified type name. + /// + /// The assembly qualified type name. + public ProvideApplicationPartFactoryAttribute(string factoryTypeName) + { + if (string.IsNullOrEmpty(factoryTypeName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(factoryTypeName)); + } + + _applicationPartFactoryTypeName = factoryTypeName; + } + + /// + /// Gets the factory type. + /// + /// + public Type GetFactoryType() + { + return _applicationPartFactoryType ?? + Type.GetType(_applicationPartFactoryTypeName, throwOnError: true); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/RelatedAssemblyAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/RelatedAssemblyAttribute.cs new file mode 100644 index 0000000000..b9f6cdb2d7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/RelatedAssemblyAttribute.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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Core; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// Specifies a assembly to load as part of MVC's assembly discovery mechanism. + /// + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public sealed class RelatedAssemblyAttribute : Attribute + { + private static readonly Func AssemblyLoadFileDelegate = Assembly.LoadFile; + + /// + /// Initializes a new instance of . + /// + /// The file name, without extension, of the related assembly. + public RelatedAssemblyAttribute(string assemblyFileName) + { + if (string.IsNullOrEmpty(assemblyFileName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(assemblyFileName)); + } + + AssemblyFileName = assemblyFileName; + } + + /// + /// Gets the assembly file name without extension. + /// + public string AssemblyFileName { get; } + + /// + /// Gets instances specified by . + /// + /// The assembly containing instances. + /// Determines if the method throws if a related assembly could not be located. + /// Related instances. + public static IReadOnlyList GetRelatedAssemblies(Assembly assembly, bool throwOnError) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + return GetRelatedAssemblies(assembly, throwOnError, File.Exists, AssemblyLoadFileDelegate); + } + + internal static IReadOnlyList GetRelatedAssemblies( + Assembly assembly, + bool throwOnError, + Func fileExists, + Func loadFile) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + // MVC will specifically look for related parts in the same physical directory as the assembly. + // No-op if the assembly does not have a location. + if (assembly.IsDynamic || string.IsNullOrEmpty(assembly.CodeBase)) + { + return Array.Empty(); + } + + var attributes = assembly.GetCustomAttributes().ToArray(); + if (attributes.Length == 0) + { + return Array.Empty(); + } + + var assemblyName = assembly.GetName().Name; + var assemblyLocation = GetAssemblyLocation(assembly); + var assemblyDirectory = Path.GetDirectoryName(assemblyLocation); + + var relatedAssemblies = new List(); + for (var i = 0; i < attributes.Length; i++) + { + var attribute = attributes[i]; + if (string.Equals(assemblyName, attribute.AssemblyFileName, StringComparison.OrdinalIgnoreCase)) + { + throw new InvalidOperationException( + Resources.FormatRelatedAssemblyAttribute_AssemblyCannotReferenceSelf(nameof(RelatedAssemblyAttribute), assemblyName)); + } + + var relatedAssemblyLocation = Path.Combine(assemblyDirectory, attribute.AssemblyFileName + ".dll"); + if (!fileExists(relatedAssemblyLocation)) + { + if (throwOnError) + { + throw new FileNotFoundException( + Resources.FormatRelatedAssemblyAttribute_CouldNotBeFound(attribute.AssemblyFileName, assemblyName, assemblyDirectory), + relatedAssemblyLocation); + } + else + { + continue; + } + } + + var relatedAssembly = loadFile(relatedAssemblyLocation); + relatedAssemblies.Add(relatedAssembly); + } + + return relatedAssemblies; + } + + internal static string GetAssemblyLocation(Assembly assembly) + { + if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out var result) && result.IsFile) + { + return result.LocalPath; + } + + return assembly.Location; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AreaAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AreaAttribute.cs new file mode 100644 index 0000000000..3e16b89297 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AreaAttribute.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 Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies the area containing a controller or action. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class AreaAttribute : RouteValueAttribute + { + /// + /// Initializes a new instance. + /// + /// The area containing the controller or action. + public AreaAttribute(string areaName) + : base("area", areaName) + { + if (string.IsNullOrEmpty(areaName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AllowAnonymousFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AllowAnonymousFilter.cs new file mode 100644 index 0000000000..34b785cbec --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AllowAnonymousFilter.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.Authorization +{ + /// + /// An implementation of + /// + public class AllowAnonymousFilter : IAllowAnonymousFilter + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs new file mode 100644 index 0000000000..abf76ea191 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs @@ -0,0 +1,222 @@ +// Copyright (c) .NET 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.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Authorization.Policy; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Authorization +{ + /// + /// An implementation of which applies a specific + /// . MVC recognizes the and adds an instance of + /// this filter to the associated action or controller. + /// + public class AuthorizeFilter : IAsyncAuthorizationFilter, IFilterFactory + { + private MvcOptions _mvcOptions; + private AuthorizationPolicy _effectivePolicy; + + /// + /// Initializes a new instance. + /// + public AuthorizeFilter() + : this(authorizeData: new[] { new AuthorizeAttribute() }) + { + } + + /// + /// Initialize a new instance. + /// + /// Authorization policy to be used. + public AuthorizeFilter(AuthorizationPolicy policy) + { + if (policy == null) + { + throw new ArgumentNullException(nameof(policy)); + } + + Policy = policy; + } + + /// + /// Initialize a new instance. + /// + /// The to use to resolve policy names. + /// The to combine into an . + public AuthorizeFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable authorizeData) + : this(authorizeData) + { + if (policyProvider == null) + { + throw new ArgumentNullException(nameof(policyProvider)); + } + + PolicyProvider = policyProvider; + } + + /// + /// Initializes a new instance of . + /// + /// The to combine into an . + public AuthorizeFilter(IEnumerable authorizeData) + { + if (authorizeData == null) + { + throw new ArgumentNullException(nameof(authorizeData)); + } + + AuthorizeData = authorizeData; + } + + /// + /// Initializes a new instance of . + /// + /// The name of the policy to require for authorization. + public AuthorizeFilter(string policy) + : this(new[] { new AuthorizeAttribute(policy) }) + { + } + + /// + /// The to use to resolve policy names. + /// + public IAuthorizationPolicyProvider PolicyProvider { get; } + + /// + /// The to combine into an . + /// + public IEnumerable AuthorizeData { get; } + + /// + /// Gets the authorization policy to be used. + /// + /// + /// Ifnull, the policy will be constructed using + /// . + /// + public AuthorizationPolicy Policy { get; } + + bool IFilterFactory.IsReusable => true; + + private async Task GetEffectivePolicyAsync(AuthorizationFilterContext context) + { + if (_effectivePolicy != null) + { + return _effectivePolicy; + } + + var effectivePolicy = Policy; + + if (_mvcOptions == null) + { + _mvcOptions = context.HttpContext.RequestServices.GetRequiredService>().Value; + } + + if (_mvcOptions.AllowCombiningAuthorizeFilters) + { + 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; + for (var i = 0; i < context.Filters.Count; i++) + { + if (ReferenceEquals(this, context.Filters[i])) + { + continue; + } + + if (context.Filters[i] is AuthorizeFilter authorizeFilter) + { + builder = builder ?? new AuthorizationPolicyBuilder(effectivePolicy); + builder.Combine(authorizeFilter.Policy); + } + } + + 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) + { + _effectivePolicy = effectivePolicy; + } + + return effectivePolicy; + } + + /// + public virtual async Task OnAuthorizationAsync(AuthorizationFilterContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var effectivePolicy = await GetEffectivePolicyAsync(context); + if (effectivePolicy == null) + { + return; + } + + var policyEvaluator = context.HttpContext.RequestServices.GetRequiredService(); + + var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext); + + // Allow Anonymous skips all authorization + if (context.Filters.Any(item => item is IAllowAnonymousFilter)) + { + return; + } + + var authorizeResult = await policyEvaluator.AuthorizeAsync(effectivePolicy, authenticateResult, context.HttpContext, context); + + if (authorizeResult.Challenged) + { + context.Result = new ChallengeResult(effectivePolicy.AuthenticationSchemes.ToArray()); + } + else if (authorizeResult.Forbidden) + { + context.Result = new ForbidResult(effectivePolicy.AuthenticationSchemes.ToArray()); + } + } + + IFilterMetadata IFilterFactory.CreateInstance(IServiceProvider serviceProvider) + { + if (Policy != null || PolicyProvider != null) + { + // The filter is fully constructed. Use the current instance to authorize. + return this; + } + + Debug.Assert(AuthorizeData != null); + var policyProvider = serviceProvider.GetRequiredService(); + return AuthorizationApplicationModelProvider.GetFilter(policyProvider, AuthorizeData); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs new file mode 100644 index 0000000000..f31552a3a6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that when executed will produce a Bad Request (400) response. + /// + public class BadRequestObjectResult : ObjectResult + { + /// + /// Creates a new instance. + /// + /// Contains the errors to be returned to the client. + public BadRequestObjectResult(object error) + : base(error) + { + StatusCode = StatusCodes.Status400BadRequest; + } + + /// + /// Creates a new instance. + /// + /// containing the validation errors. + public BadRequestObjectResult(ModelStateDictionary modelState) + : base(new SerializableError(modelState)) + { + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + StatusCode = StatusCodes.Status400BadRequest; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BadRequestResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BadRequestResult.cs new file mode 100644 index 0000000000..69025ee49d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BadRequestResult.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 Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A that when + /// executed will produce a Bad Request (400) response. + /// + public class BadRequestResult : StatusCodeResult + { + /// + /// Creates a new instance. + /// + public BadRequestResult() + : base(StatusCodes.Status400BadRequest) + { + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindAttribute.cs new file mode 100644 index 0000000000..fe4f9e7996 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindAttribute.cs @@ -0,0 +1,85 @@ +// Copyright (c) .NET 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; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// This attribute can be used on action parameters and types, to indicate model level metadata. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public class BindAttribute : Attribute, IModelNameProvider, IPropertyFilterProvider + { + private static readonly Func _default = (m) => true; + + private Func _propertyFilter; + + /// + /// Creates a new instance of . + /// + /// Names of parameters to include in binding. + public BindAttribute(params string[] include) + { + var items = new List(); + foreach (var item in include) + { + items.AddRange(SplitString(item)); + } + + Include = items.ToArray(); + } + + /// + /// Gets the names of properties to include in model binding. + /// + public string[] Include { get; } + + /// + /// Allows a user to specify a particular prefix to match during model binding. + /// + // This property is exposed for back compat reasons. + public string Prefix { get; set; } + + /// + /// Represents the model name used during model binding. + /// + string IModelNameProvider.Name => Prefix; + + /// + public Func PropertyFilter + { + get + { + if (Include != null && Include.Length > 0) + { + if (_propertyFilter == null) + { + _propertyFilter = (m) => Include.Contains(m.PropertyName, StringComparer.Ordinal); + } + + return _propertyFilter; + } + else + { + return _default; + } + } + } + + private static IEnumerable SplitString(string original) + { + if (string.IsNullOrEmpty(original)) + { + return Array.Empty(); + } + + var split = original.Split(',').Select(piece => piece.Trim()).Where(piece => !string.IsNullOrEmpty(piece)); + + return split; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindPropertiesAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindPropertiesAttribute.cs new file mode 100644 index 0000000000..28a1558290 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindPropertiesAttribute.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 Microsoft.AspNetCore.Mvc +{ + /// + /// An attribute that enables binding for all properties the decorated controller or Razor Page model defines. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public class BindPropertiesAttribute : Attribute + { + /// + /// When true, allows properties to be bound on GET requests. When false, properties + /// do not get model bound or validated on GET requests. + /// + /// Defaults to false. + /// + /// + public bool SupportsGet { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindPropertyAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindPropertyAttribute.cs new file mode 100644 index 0000000000..2058d7d907 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindPropertyAttribute.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 Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class BindPropertyAttribute : Attribute, IModelNameProvider, IBinderTypeProviderMetadata, IRequestPredicateProvider + { + private static readonly Func _supportsAllRequests = (c) => true; + + private static readonly Func _supportsNonGetRequests = IsNonGetRequest; + + private BindingSource _bindingSource; + + public bool SupportsGet { get; set; } + + public Type BinderType { get; set; } + + /// + public virtual BindingSource BindingSource + { + get + { + if (_bindingSource == null && BinderType != null) + { + return BindingSource.Custom; + } + + return _bindingSource; + } + protected set => _bindingSource = value; + } + + /// + public string Name { get; set; } + + Func IRequestPredicateProvider.RequestPredicate + => SupportsGet ? _supportsAllRequests : _supportsNonGetRequests; + + private static bool IsNonGetRequest(ActionContext context) + { + return !string.Equals(context.HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..2dcbcd8fe4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -0,0 +1,103 @@ +// Copyright (c) .NET 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.Internal; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Builder +{ + /// + /// Extension methods for to add MVC to the request execution pipeline. + /// + public static class MvcApplicationBuilderExtensions + { + /// + /// Adds MVC to the request execution pipeline. + /// + /// The . + /// A reference to this instance after the operation has completed. + /// This method only supports attribute routing. To add conventional routes use + /// . + public static IApplicationBuilder UseMvc(this IApplicationBuilder app) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + return app.UseMvc(routes => + { + }); + } + + /// + /// Adds MVC to the request execution pipeline + /// with a default route named 'default' and the following template: + /// '{controller=Home}/{action=Index}/{id?}'. + /// + /// The . + /// A reference to this instance after the operation has completed. + public static IApplicationBuilder UseMvcWithDefaultRoute(this IApplicationBuilder app) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + return app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + } + + /// + /// Adds MVC to the request execution pipeline. + /// + /// The . + /// A callback to configure MVC routes. + /// A reference to this instance after the operation has completed. + public static IApplicationBuilder UseMvc( + this IApplicationBuilder app, + Action configureRoutes) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + if (configureRoutes == null) + { + 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(...)")); + } + + var middlewarePipelineBuilder = app.ApplicationServices.GetRequiredService(); + middlewarePipelineBuilder.ApplicationBuilder = app.New(); + + var routes = new RouteBuilder(app) + { + DefaultHandler = app.ApplicationServices.GetRequiredService(), + }; + + configureRoutes(routes); + + routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)); + + return app.UseRouter(routes.Build()); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcAreaRouteBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcAreaRouteBuilderExtensions.cs new file mode 100644 index 0000000000..f5db1df3b1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcAreaRouteBuilderExtensions.cs @@ -0,0 +1,141 @@ +// Copyright (c) .NET 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.Routing; +using Microsoft.AspNetCore.Routing.Constraints; + +namespace Microsoft.AspNetCore.Builder +{ + /// + /// Extension methods for . + /// + public static class MvcAreaRouteBuilderExtensions + { + /// + /// Adds a route to the with the given MVC area with the specified + /// , and . + /// + /// The to add the route to. + /// The name of the route. + /// The MVC area name. + /// The URL pattern of the route. + /// A reference to this instance after the operation has completed. + public static IRouteBuilder MapAreaRoute( + this IRouteBuilder routeBuilder, + string name, + string areaName, + string template) + { + MapAreaRoute(routeBuilder, name, areaName, template, defaults: null, constraints: null, dataTokens: null); + return routeBuilder; + } + + /// + /// Adds a route to the with the given MVC area with the specified + /// , , , and + /// . + /// + /// The to add the route to. + /// The name of the route. + /// The MVC area name. + /// The URL pattern of the route. + /// + /// An object that contains default values for route 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 IRouteBuilder MapAreaRoute( + this IRouteBuilder routeBuilder, + string name, + string areaName, + string template, + object defaults) + { + MapAreaRoute(routeBuilder, name, areaName, template, defaults, constraints: null, dataTokens: null); + return routeBuilder; + } + + /// + /// Adds a route to the with the given MVC area with the specified + /// , , , + /// , and . + /// + /// The to add the route to. + /// The name of the route. + /// The MVC area name. + /// The URL pattern of the route. + /// + /// An object that contains default values for route parameters. The object's properties represent the + /// names and values of the default values. + /// + /// + /// An object that contains constraints for the route. The object's properties represent the names and + /// values of the constraints. + /// + /// A reference to this instance after the operation has completed. + public static IRouteBuilder MapAreaRoute( + this IRouteBuilder routeBuilder, + string name, + string areaName, + string template, + object defaults, + object constraints) + { + MapAreaRoute(routeBuilder, name, areaName, template, defaults, constraints, dataTokens: null); + return routeBuilder; + } + + /// + /// Adds a route to the with the given MVC area with the specified + /// , , , + /// , , and . + /// + /// The to add the route to. + /// The name of the route. + /// The MVC area name. + /// The URL pattern of the route. + /// + /// An object that contains default values for route parameters. The object's properties represent the + /// names and values of the default values. + /// + /// + /// An object that contains constraints for the route. The object's properties represent the names and + /// values of the constraints. + /// + /// + /// An object that contains data tokens for the route. 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 IRouteBuilder MapAreaRoute( + this IRouteBuilder routeBuilder, + string name, + string areaName, + string template, + object defaults, + object constraints, + object dataTokens) + { + if (routeBuilder == null) + { + throw new ArgumentNullException(nameof(routeBuilder)); + } + + 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); + + routeBuilder.MapRoute(name, template, defaultsDictionary, constraintsDictionary, dataTokens); + return routeBuilder; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CacheProfile.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CacheProfile.cs new file mode 100644 index 0000000000..15998b31ea --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CacheProfile.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. + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Defines a set of settings which can be used for response caching. + /// + public class CacheProfile + { + /// + /// Gets or sets the duration in seconds for which the response is cached. + /// If this property is set to a non null value, + /// the "max-age" in "Cache-control" header is set in the + /// . + /// + public int? Duration { get; set; } + + /// + /// Gets or sets the location where the data from a particular URL must be cached. + /// If this property is set to a non null value, + /// the "Cache-control" header is set in the . + /// + public ResponseCacheLocation? Location { get; set; } + + /// + /// Gets or sets the value which determines whether the data should be stored or not. + /// When set to , it sets "Cache-control" header in + /// to "no-store". + /// Ignores the "Location" parameter for values other than "None". + /// Ignores the "Duration" parameter. + /// + public bool? NoStore { get; set; } + + /// + /// Gets or sets the value for the Vary header in . + /// + public string VaryByHeader { get; set; } + + /// + /// Gets or sets the query keys to vary by. + /// + /// + /// requires the response cache middleware. + /// + public string[] VaryByQueryKeys { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ChallengeResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ChallengeResult.cs new file mode 100644 index 0000000000..7f7f0249ca --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ChallengeResult.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; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that on execution invokes . + /// + public class ChallengeResult : ActionResult + { + /// + /// Initializes a new instance of . + /// + public ChallengeResult() + : this(Array.Empty()) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication scheme. + /// + /// The authentication scheme to challenge. + public ChallengeResult(string authenticationScheme) + : this(new[] { authenticationScheme }) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication schemes. + /// + /// The authentication schemes to challenge. + public ChallengeResult(IList authenticationSchemes) + : this(authenticationSchemes, properties: null) + { + } + + /// + /// Initializes a new instance of with the + /// specified . + /// + /// used to perform the authentication + /// challenge. + public ChallengeResult(AuthenticationProperties properties) + : this(Array.Empty(), properties) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication scheme and . + /// + /// The authentication schemes to challenge. + /// used to perform the authentication + /// challenge. + public ChallengeResult(string authenticationScheme, AuthenticationProperties properties) + : this(new[] { authenticationScheme }, properties) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication schemes and . + /// + /// The authentication scheme to challenge. + /// used to perform the authentication + /// challenge. + public ChallengeResult(IList authenticationSchemes, AuthenticationProperties properties) + { + AuthenticationSchemes = authenticationSchemes; + Properties = properties; + } + + /// + /// Gets or sets the authentication schemes that are challenged. + /// + public IList AuthenticationSchemes { get; set; } + + /// + /// Gets or sets the used to perform the authentication challenge. + /// + public AuthenticationProperties Properties { get; set; } + + /// + public override async Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); + var logger = loggerFactory.CreateLogger(); + + logger.ChallengeResultExecuting(AuthenticationSchemes); + + if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0) + { + foreach (var scheme in AuthenticationSchemes) + { + await context.HttpContext.ChallengeAsync(scheme, Properties); + } + } + else + { + await context.HttpContext.ChallengeAsync(Properties); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs new file mode 100644 index 0000000000..91372a0d19 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.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 Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies the version compatibility of runtime behaviors configured by . + /// + /// + /// + /// The best way to set a compatibility version is by using + /// or + /// in your application's + /// ConfigureServices method. + /// + /// Setting the compatibility version using : + /// + /// 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. + /// + /// + public enum CompatibilityVersion + { + /// + /// 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 + /// ASP.NET Core MVC 2.1. + /// + /// + /// ASP.NET Core MVC 2.1 introduces compatibility switches for the following: + /// + /// + /// + /// MvcJsonOptions.AllowInputFormatterExceptionMessages + /// RazorPagesOptions.AllowAreas + /// + /// + Version_2_1, + + /// + /// 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 . + /// + Latest = int.MaxValue, + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs new file mode 100644 index 0000000000..29e72e816b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that when executed will produce a Conflict (409) response. + /// + public class ConflictObjectResult : ObjectResult + { + /// + /// Creates a new instance. + /// + /// Contains the errors to be returned to the client. + public ConflictObjectResult(object error) + : base(error) + { + StatusCode = StatusCodes.Status409Conflict; + } + + /// + /// Creates a new instance. + /// + /// containing the validation errors. + public ConflictObjectResult(ModelStateDictionary modelState) + : base(new SerializableError(modelState)) + { + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + StatusCode = StatusCodes.Status409Conflict; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConflictResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConflictResult.cs new file mode 100644 index 0000000000..92eb10385a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConflictResult.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 +{ + /// + /// A that when executed will produce a Conflict (409) response. + /// + public class ConflictResult : StatusCodeResult + { + /// + /// Creates a new instance. + /// + public ConflictResult() + : base(StatusCodes.Status409Conflict) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs new file mode 100644 index 0000000000..c3c240e63e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs @@ -0,0 +1,231 @@ +// Copyright (c) .NET 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.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.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A filter that specifies the supported request content types. is used to select an + /// action when there would otherwise be multiple matches. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class ConsumesAttribute : + Attribute, + IResourceFilter, + IConsumesActionConstraint, + IApiRequestMetadataProvider + { + public static readonly int ConsumesActionConstraintOrder = 200; + + /// + /// Creates a new instance of . + /// + public ConsumesAttribute(string contentType, params string[] otherContentTypes) + { + if (contentType == null) + { + throw new ArgumentNullException(nameof(contentType)); + } + + // We want to ensure that the given provided content types are valid values, so + // we validate them using the semantics of MediaTypeHeaderValue. + MediaTypeHeaderValue.Parse(contentType); + + for (var i = 0; i < otherContentTypes.Length; i++) + { + MediaTypeHeaderValue.Parse(otherContentTypes[i]); + } + + ContentTypes = GetContentTypes(contentType, otherContentTypes); + } + + // The value used is a non default value so that it avoids getting mixed with other action constraints + // with default order. + /// + int IActionConstraint.Order => ConsumesActionConstraintOrder; + + /// + /// Gets or sets the supported request content types. Used to select an action when there would otherwise be + /// multiple matches. + /// + public MediaTypeCollection ContentTypes { get; set; } + + /// + public void OnResourceExecuting(ResourceExecutingContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // Only execute if the current filter is the one which is closest to the action. + // Ignore all other filters. This is to ensure we have a overriding behavior. + if (IsApplicable(context.ActionDescriptor)) + { + var requestContentType = context.HttpContext.Request.ContentType; + + // 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)) + { + context.Result = new UnsupportedMediaTypeResult(); + } + } + } + + private bool IsSubsetOfAnyContentType(string requestMediaType) + { + var parsedRequestMediaType = new MediaType(requestMediaType); + for (var i = 0; i < ContentTypes.Count; i++) + { + var contentTypeMediaType = new MediaType(ContentTypes[i]); + if (parsedRequestMediaType.IsSubsetOf(contentTypeMediaType)) + { + return true; + } + } + return false; + } + + /// + public void OnResourceExecuted(ResourceExecutedContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + } + + /// + public bool Accept(ActionConstraintContext context) + { + // If this constraint is not closest to the action, it will be skipped. + if (!IsApplicable(context.CurrentCandidate.Action)) + { + // 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 action. + return true; + } + + var requestContentType = context.RouteContext.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 actions with consumes action constraints this should result in ambiguous exception + // unless there is another action without a consumes constraint. + if (requestContentType == null) + { + var isActionWithoutConsumeConstraintPresent = context.Candidates.Any( + candidate => candidate.Constraints == null || + !candidate.Constraints.Any(constraint => constraint is IConsumesActionConstraint)); + + return !isActionWithoutConsumeConstraintPresent; + } + + // 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 (IsSubsetOfAnyContentType(requestContentType)) + { + return true; + } + + var firstCandidate = context.Candidates[0]; + if (firstCandidate.Action != context.CurrentCandidate.Action) + { + // 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 IConsumesActionConstraints 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.Action == firstCandidate.Action) + { + continue; + } + + var tempContext = new ActionConstraintContext() + { + Candidates = context.Candidates, + RouteContext = context.RouteContext, + CurrentCandidate = candidate + }; + + if (candidate.Constraints == null || candidate.Constraints.Count == 0 || + candidate.Constraints.Any(constraint => constraint is IConsumesActionConstraint && + 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 + // 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 actionDescriptor.FilterDescriptors.Last( + filter => filter.Filter is IConsumesActionConstraint).Filter == this; + + } + + private MediaTypeCollection GetContentTypes(string firstArg, string[] args) + { + var completeArgs = new List(); + completeArgs.Add(firstArg); + completeArgs.AddRange(args); + var contentTypes = new MediaTypeCollection(); + foreach (var arg in completeArgs) + { + var mediaType = new MediaType(arg); + if (mediaType.MatchesAllSubTypes || + mediaType.MatchesAllTypes) + { + throw new InvalidOperationException( + Resources.FormatMatchAllContentTypeIsNotAllowed(arg)); + } + + contentTypes.Add(arg); + } + + return contentTypes; + } + + /// + public void SetContentTypes(MediaTypeCollection contentTypes) + { + contentTypes.Clear(); + foreach (var contentType in ContentTypes) + { + contentTypes.Add(contentType); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ContentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ContentResult.cs new file mode 100644 index 0000000000..65ad59bf47 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ContentResult.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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc +{ + public class ContentResult : ActionResult + { + /// + /// Gets or set the content representing the body of the response. + /// + public string Content { get; set; } + + /// + /// Gets or sets the Content-Type header for the response. + /// + public string ContentType { get; set; } + + /// + /// Gets or sets the HTTP status code. + /// + public int? StatusCode { get; set; } + + public override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var executor = context.HttpContext.RequestServices.GetRequiredService>(); + return executor.ExecuteAsync(context, this); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerAttribute.cs new file mode 100644 index 0000000000..2e225dac6d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerAttribute.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 System; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Indicates that the type and any derived types that this attribute is applied to + /// are considered a controller by the default controller discovery mechanism, unless + /// is applied to any type in the hierarchy. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public class ControllerAttribute : Attribute + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs new file mode 100644 index 0000000000..5626f1ccd2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs @@ -0,0 +1,2705 @@ +// Copyright (c) .NET 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.Expressions; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A base class for an MVC controller without view support. + /// + [Controller] + public abstract class ControllerBase + { + private ControllerContext _controllerContext; + private IModelMetadataProvider _metadataProvider; + private IModelBinderFactory _modelBinderFactory; + private IObjectModelValidator _objectValidator; + private IUrlHelper _url; + + /// + /// Gets the for the executing action. + /// + public HttpContext HttpContext => ControllerContext.HttpContext; + + /// + /// Gets the for the executing action. + /// + public HttpRequest Request => HttpContext?.Request; + + /// + /// Gets the for the executing action. + /// + public HttpResponse Response => HttpContext?.Response; + + /// + /// Gets the for the executing action. + /// + public RouteData RouteData => ControllerContext.RouteData; + + /// + /// Gets the that contains the state of the model and of model-binding validation. + /// + public ModelStateDictionary ModelState => ControllerContext.ModelState; + + /// + /// Gets or sets the . + /// + /// + /// activates this property while activating controllers. + /// If user code directly instantiates a controller, the getter returns an empty + /// . + /// + [ControllerContext] + public ControllerContext ControllerContext + { + get + { + if (_controllerContext == null) + { + _controllerContext = new ControllerContext(); + } + + return _controllerContext; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _controllerContext = value; + } + } + + /// + /// Gets or sets the . + /// + public IModelMetadataProvider MetadataProvider + { + get + { + if (_metadataProvider == null) + { + _metadataProvider = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _metadataProvider; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _metadataProvider = value; + } + } + + /// + /// Gets or sets the . + /// + public IModelBinderFactory ModelBinderFactory + { + get + { + if (_modelBinderFactory == null) + { + _modelBinderFactory = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _modelBinderFactory; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _modelBinderFactory = value; + } + } + + /// + /// Gets or sets the . + /// + public IUrlHelper Url + { + get + { + if (_url == null) + { + var factory = HttpContext?.RequestServices?.GetRequiredService(); + _url = factory?.GetUrlHelper(ControllerContext); + } + + return _url; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _url = value; + } + } + + /// + /// Gets or sets the . + /// + public IObjectModelValidator ObjectValidator + { + get + { + if (_objectValidator == null) + { + _objectValidator = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _objectValidator; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _objectValidator = value; + } + } + + /// + /// Gets the for user associated with the executing action. + /// + public ClaimsPrincipal User => HttpContext?.User; + + /// + /// Creates a object by specifying a . + /// + /// The status code to set on the response. + /// The created object for the response. + [NonAction] + public virtual StatusCodeResult StatusCode(int statusCode) + => new StatusCodeResult(statusCode); + + /// + /// Creates a object by specifying a and + /// + /// The status code to set on the response. + /// The value to set on the . + /// The created object for the response. + [NonAction] + public virtual ObjectResult StatusCode(int statusCode, object value) + { + var result = new ObjectResult(value); + result.StatusCode = statusCode; + + return result; + } + + /// + /// Creates a object with by specifying a + /// string. + /// + /// The content to write to the response. + /// The created object for the response. + [NonAction] + public virtual ContentResult Content(string content) + => Content(content, (MediaTypeHeaderValue)null); + + /// + /// Creates a object with by specifying a + /// string and a content type. + /// + /// The content to write to the response. + /// The content type (MIME type). + /// The created object for the response. + [NonAction] + public virtual ContentResult Content(string content, string contentType) + => Content(content, MediaTypeHeaderValue.Parse(contentType)); + + /// + /// Creates a object with by specifying a + /// string, a , and . + /// + /// The content to write to the response. + /// The content type (MIME type). + /// The content encoding. + /// The created object for the response. + /// + /// If encoding is provided by both the 'charset' and the parameters, then + /// the parameter is chosen as the final encoding. + /// + [NonAction] + public virtual ContentResult Content(string content, string contentType, Encoding contentEncoding) + { + var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType); + mediaTypeHeaderValue.Encoding = contentEncoding ?? mediaTypeHeaderValue.Encoding; + return Content(content, mediaTypeHeaderValue); + } + + /// + /// Creates a object with by specifying a + /// string and a . + /// + /// The content to write to the response. + /// The content type (MIME type). + /// The created object for the response. + [NonAction] + public virtual ContentResult Content(string content, MediaTypeHeaderValue contentType) + { + var result = new ContentResult + { + Content = content, + ContentType = contentType?.ToString() + }; + + return result; + } + + /// + /// Creates a object that produces an empty + /// response. + /// + /// The created object for the response. + [NonAction] + public virtual NoContentResult NoContent() + => new NoContentResult(); + + /// + /// Creates a object that produces an empty response. + /// + /// The created for the response. + [NonAction] + public virtual OkResult Ok() + => new OkResult(); + + /// + /// Creates an object that produces an response. + /// + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual OkObjectResult Ok(object value) + => new OkObjectResult(value); + + /// + /// Creates a object that redirects () + /// to the specified . + /// + /// The URL to redirect to. + /// The created for the response. + [NonAction] + public virtual RedirectResult Redirect(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url); + } + + /// + /// Creates a object with set to true + /// () using the specified . + /// + /// The URL to redirect to. + /// The created for the response. + [NonAction] + public virtual RedirectResult RedirectPermanent(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url, permanent: true); + } + + /// + /// Creates a object with set to false + /// and set to true () + /// using the specified . + /// + /// The URL to redirect to. + /// The created for the response. + [NonAction] + public virtual RedirectResult RedirectPreserveMethod(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url: url, permanent: false, preserveMethod: true); + } + + /// + /// Creates a object with set to true + /// and set to true () + /// using the specified . + /// + /// The URL to redirect to. + /// The created for the response. + [NonAction] + public virtual RedirectResult RedirectPermanentPreserveMethod(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url: url, permanent: true, preserveMethod: true); + } + + /// + /// Creates a object that redirects + /// () to the specified local . + /// + /// The local URL to redirect to. + /// The created for the response. + [NonAction] + public virtual LocalRedirectResult LocalRedirect(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl); + } + + /// + /// Creates a object with set to + /// true () using the specified . + /// + /// The local URL to redirect to. + /// The created for the response. + [NonAction] + public virtual LocalRedirectResult LocalRedirectPermanent(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl, permanent: true); + } + + /// + /// Creates a object with set to + /// false and set to true + /// () using the specified . + /// + /// The local URL to redirect to. + /// The created for the response. + [NonAction] + public virtual LocalRedirectResult LocalRedirectPreserveMethod(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl: localUrl, permanent: false, preserveMethod: true); + } + + /// + /// Creates a object with set to + /// true and set to true + /// () using the specified . + /// + /// The local URL to redirect to. + /// The created for the response. + [NonAction] + public virtual LocalRedirectResult LocalRedirectPermanentPreserveMethod(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl: localUrl, permanent: true, preserveMethod: true); + } + + /// + /// Redirects () to an action with the same name as current one. + /// The 'controller' and 'action' names are retrieved from the ambient values of the current request. + /// + /// The created for the response. + /// + /// A POST request to an action named "Product" updates a product and redirects to an action, also named + /// "Product", showing details of the updated product. + /// + /// [HttpGet] + /// public IActionResult Product(int id) + /// { + /// var product = RetrieveProduct(id); + /// return View(product); + /// } + /// + /// [HttpPost] + /// public IActionResult Product(int id, Product product) + /// { + /// UpdateProduct(product); + /// return RedirectToAction(); + /// } + /// + /// + [NonAction] + public virtual RedirectToActionResult RedirectToAction() + => RedirectToAction(actionName: null); + + /// + /// Redirects () to the specified action using the . + /// + /// The name of the action. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToAction(string actionName) + => RedirectToAction(actionName, routeValues: null); + + /// + /// Redirects () to the specified action using the + /// and . + /// + /// The name of the action. + /// The parameters for a route. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToAction(string actionName, object routeValues) + => RedirectToAction(actionName, controllerName: null, routeValues: routeValues); + + /// + /// Redirects () to the specified action using the + /// and the . + /// + /// The name of the action. + /// The name of the controller. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToAction(string actionName, string controllerName) + => RedirectToAction(actionName, controllerName, routeValues: null); + + /// + /// Redirects () to the specified action using the specified + /// , , and . + /// + /// The name of the action. + /// The name of the controller. + /// The parameters for a route. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToAction( + string actionName, + string controllerName, + object routeValues) + => RedirectToAction(actionName, controllerName, routeValues, fragment: null); + + /// + /// Redirects () to the specified action using the specified + /// , , and . + /// + /// The name of the action. + /// The name of the controller. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToAction( + string actionName, + string controllerName, + string fragment) + => RedirectToAction(actionName, controllerName, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified action using the specified , + /// , , and . + /// + /// The name of the action. + /// The name of the controller. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToAction( + string actionName, + string controllerName, + object routeValues, + string fragment) + { + return new RedirectToActionResult(actionName, controllerName, routeValues, fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified action with + /// set to false and + /// set to true, using the specified , , + /// , and . + /// + /// The name of the action. + /// The name of the controller. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToActionPreserveMethod( + string actionName = null, + string controllerName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToActionResult( + actionName: actionName, + controllerName: controllerName, + routeValues: routeValues, + permanent: false, + preserveMethod: true, + fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified action with + /// set to true using the specified . + /// + /// The name of the action. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToActionPermanent(string actionName) + => RedirectToActionPermanent(actionName, routeValues: null); + + /// + /// Redirects () to the specified action with + /// set to true using the specified + /// and . + /// + /// The name of the action. + /// The parameters for a route. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToActionPermanent(string actionName, object routeValues) + => RedirectToActionPermanent(actionName, controllerName: null, routeValues: routeValues); + + /// + /// Redirects () to the specified action with + /// set to true using the specified + /// and . + /// + /// The name of the action. + /// The name of the controller. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName) + => RedirectToActionPermanent(actionName, controllerName, routeValues: null); + + /// + /// Redirects () to the specified action with + /// set to true using the specified , + /// , and . + /// + /// The name of the action. + /// The name of the controller. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToActionPermanent( + string actionName, + string controllerName, + string fragment) + => RedirectToActionPermanent(actionName, controllerName, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified action with + /// set to true using the specified , + /// , and . + /// + /// The name of the action. + /// The name of the controller. + /// The parameters for a route. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToActionPermanent( + string actionName, + string controllerName, + object routeValues) + => RedirectToActionPermanent(actionName, controllerName, routeValues, fragment: null); + + /// + /// Redirects () to the specified action with + /// set to true using the specified , + /// , , and . + /// + /// The name of the action. + /// The name of the controller. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToActionPermanent( + string actionName, + string controllerName, + object routeValues, + string fragment) + { + return new RedirectToActionResult( + actionName, + controllerName, + routeValues, + permanent: true, + fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified action with + /// set to true and + /// set to true, using the specified , , + /// , and . + /// + /// The name of the action. + /// The name of the controller. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToActionResult RedirectToActionPermanentPreserveMethod( + string actionName = null, + string controllerName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToActionResult( + actionName: actionName, + controllerName: controllerName, + routeValues: routeValues, + permanent: true, + preserveMethod: true, + fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified route using the specified . + /// + /// The name of the route. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoute(string routeName) + => RedirectToRoute(routeName, routeValues: null); + + /// + /// Redirects () to the specified route using the specified . + /// + /// The parameters for a route. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoute(object routeValues) + => RedirectToRoute(routeName: null, routeValues: routeValues); + + /// + /// Redirects () to the specified route using the specified + /// and . + /// + /// The name of the route. + /// The parameters for a route. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoute(string routeName, object routeValues) + => RedirectToRoute(routeName, routeValues, fragment: null); + + /// + /// Redirects () to the specified route using the specified + /// and . + /// + /// The name of the route. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoute(string routeName, string fragment) + => RedirectToRoute(routeName, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified route using the specified + /// , , and . + /// + /// The name of the route. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoute( + string routeName, + object routeValues, + string fragment) + { + return new RedirectToRouteResult(routeName, routeValues, fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified route with + /// set to false and + /// set to true, using the specified , , and . + /// + /// The name of the route. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoutePreserveMethod( + string routeName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToRouteResult( + routeName: routeName, + routeValues: routeValues, + permanent: false, + preserveMethod: true, + fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified route with + /// set to true using the specified . + /// + /// The name of the route. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName) + => RedirectToRoutePermanent(routeName, routeValues: null); + + /// + /// Redirects () to the specified route with + /// set to true using the specified . + /// + /// The parameters for a route. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoutePermanent(object routeValues) + => RedirectToRoutePermanent(routeName: null, routeValues: routeValues); + + /// + /// Redirects () to the specified route with + /// set to true using the specified + /// and . + /// + /// The name of the route. + /// The parameters for a route. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, object routeValues) + => RedirectToRoutePermanent(routeName, routeValues, fragment: null); + + /// + /// Redirects () to the specified route with + /// set to true using the specified + /// and . + /// + /// The name of the route. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, string fragment) + => RedirectToRoutePermanent(routeName, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified route with + /// set to true using the specified , + /// , and . + /// + /// The name of the route. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoutePermanent( + string routeName, + object routeValues, + string fragment) + { + return new RedirectToRouteResult(routeName, routeValues, permanent: true, fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified route with + /// set to true and + /// set to true, using the specified , , and . + /// + /// The name of the route. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToRouteResult RedirectToRoutePermanentPreserveMethod( + string routeName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToRouteResult( + routeName: routeName, + routeValues: routeValues, + permanent: true, + preserveMethod: true, + fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified . + /// + /// The name of the page. + /// The . + [NonAction] + public virtual RedirectToPageResult RedirectToPage(string pageName) + => RedirectToPage(pageName, routeValues: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The parameters for a route. + /// The . + [NonAction] + public virtual RedirectToPageResult RedirectToPage(string pageName, object routeValues) + => RedirectToPage(pageName, pageHandler: null, routeValues: routeValues, fragment: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The . + [NonAction] + public virtual RedirectToPageResult RedirectToPage(string pageName, string pageHandler) + => RedirectToPage(pageName, pageHandler, routeValues: null); + + /// + /// Redirects () to the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for a route. + /// The . + [NonAction] + public virtual RedirectToPageResult RedirectToPage(string pageName, string pageHandler, object routeValues) + => RedirectToPage(pageName, pageHandler, routeValues, fragment: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The fragment to add to the URL. + /// The . + [NonAction] + public virtual RedirectToPageResult RedirectToPage(string pageName, string pageHandler, string fragment) + => RedirectToPage(pageName, pageHandler, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified + /// using the specified and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The . + [NonAction] + public virtual RedirectToPageResult RedirectToPage(string pageName, string pageHandler, object routeValues, string fragment) + => new RedirectToPageResult(pageName, pageHandler, routeValues, fragment); + + /// + /// Redirects () to the specified . + /// + /// The name of the page. + /// The with set. + [NonAction] + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName) + => RedirectToPagePermanent(pageName, routeValues: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The parameters for a route. + /// The with set. + [NonAction] + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, object routeValues) + => RedirectToPagePermanent(pageName, pageHandler: null, routeValues: routeValues, fragment: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The with set. + [NonAction] + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, string pageHandler) + => RedirectToPagePermanent(pageName, pageHandler, routeValues: null, fragment: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The fragment to add to the URL. + /// The with set. + [NonAction] + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, string pageHandler, string fragment) + => RedirectToPagePermanent(pageName, pageHandler, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified + /// using the specified and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The with set. + [NonAction] + public virtual RedirectToPageResult RedirectToPagePermanent( + string pageName, + string pageHandler, + object routeValues, + string fragment) + => new RedirectToPageResult(pageName, pageHandler, routeValues, permanent: true, fragment: fragment); + + /// + /// Redirects () to the specified page with + /// set to false and + /// set to true, using the specified , , and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToPageResult RedirectToPagePreserveMethod( + string pageName, + string pageHandler = null, + object routeValues = null, + string fragment = null) + { + if (pageName == null) + { + throw new ArgumentNullException(nameof(pageName)); + } + + return new RedirectToPageResult( + pageName: pageName, + pageHandler: pageHandler, + routeValues: routeValues, + permanent: false, + preserveMethod: true, + fragment: fragment); + } + + /// + /// Redirects () to the specified route with + /// set to true and + /// set to true, using the specified , , and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + [NonAction] + public virtual RedirectToPageResult RedirectToPagePermanentPreserveMethod( + string pageName, + string pageHandler = null, + object routeValues = null, + string fragment = null) + { + if (pageName == null) + { + throw new ArgumentNullException(nameof(pageName)); + } + + return new RedirectToPageResult( + pageName: pageName, + pageHandler: pageHandler, + routeValues: routeValues, + permanent: true, + preserveMethod: true, + fragment: fragment); + } + + /// + /// Returns a file with the specified as content (), + /// and the specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The file contents. + /// The Content-Type of the file. + /// The created for the response. + [NonAction] + public virtual FileContentResult File(byte[] fileContents, string contentType) + => File(fileContents, contentType, fileDownloadName: null); + + /// + /// Returns a file with the specified as content (), + /// and the specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The file contents. + /// The Content-Type of the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual FileContentResult File(byte[] fileContents, string contentType, bool enableRangeProcessing) + => File(fileContents, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing); + + /// + /// Returns a file with the specified as content (), the + /// specified as the Content-Type and the specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The file contents. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + [NonAction] + public virtual FileContentResult File(byte[] fileContents, string contentType, string fileDownloadName) + => new FileContentResult(fileContents, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Returns a file with the specified as content (), the + /// specified as the Content-Type and the specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The file contents. + /// The Content-Type of the file. + /// The suggested file name. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual FileContentResult File(byte[] fileContents, string contentType, string fileDownloadName, bool enableRangeProcessing) + => new FileContentResult(fileContents, contentType) + { + FileDownloadName = fileDownloadName, + EnableRangeProcessing = enableRangeProcessing, + }; + + /// + /// Returns a file with the specified as content (), + /// and the specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The file contents. + /// The Content-Type of the file. + /// The of when the file was last modified. + /// The associated with the file. + /// The created for the response. + [NonAction] + public virtual FileContentResult File(byte[] fileContents, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag) + { + return new FileContentResult(fileContents, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + }; + } + + /// + /// Returns a file with the specified as content (), + /// and the specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The file contents. + /// The Content-Type of the file. + /// The of when the file was last modified. + /// The associated with the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual FileContentResult File(byte[] fileContents, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing) + { + return new FileContentResult(fileContents, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + EnableRangeProcessing = enableRangeProcessing, + }; + } + + /// + /// Returns a file with the specified as content (), the + /// specified as the Content-Type, and the specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The file contents. + /// The Content-Type of the file. + /// The suggested file name. + /// The of when the file was last modified. + /// The associated with the file. + /// The created for the response. + [NonAction] + public virtual FileContentResult File(byte[] fileContents, string contentType, string fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag) + { + return new FileContentResult(fileContents, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + FileDownloadName = fileDownloadName, + }; + } + + /// + /// Returns a file with the specified as content (), the + /// specified as the Content-Type, and the specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The file contents. + /// The Content-Type of the file. + /// The suggested file name. + /// The of when the file was last modified. + /// The associated with the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual FileContentResult File(byte[] fileContents, string contentType, string fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing) + { + return new FileContentResult(fileContents, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + FileDownloadName = fileDownloadName, + EnableRangeProcessing = enableRangeProcessing, + }; + } + + /// + /// Returns a file in the specified (), with the + /// specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// The created for the response. + [NonAction] + public virtual FileStreamResult File(Stream fileStream, string contentType) + => File(fileStream, contentType, fileDownloadName: null); + + /// + /// Returns a file in the specified (), with the + /// specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual FileStreamResult File(Stream fileStream, string contentType, bool enableRangeProcessing) + => File(fileStream, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing); + + /// + /// Returns a file in the specified () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + [NonAction] + public virtual FileStreamResult File(Stream fileStream, string contentType, string fileDownloadName) + => new FileStreamResult(fileStream, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Returns a file in the specified () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// The suggested file name. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual FileStreamResult File(Stream fileStream, string contentType, string fileDownloadName, bool enableRangeProcessing) + => new FileStreamResult(fileStream, contentType) + { + FileDownloadName = fileDownloadName, + EnableRangeProcessing = enableRangeProcessing, + }; + + /// + /// Returns a file in the specified (), + /// and the specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// The of when the file was last modified. + /// The associated with the file. + /// The created for the response. + [NonAction] + public virtual FileStreamResult File(Stream fileStream, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag) + { + return new FileStreamResult(fileStream, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + }; + } + + /// + /// Returns a file in the specified (), + /// and the specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// The of when the file was last modified. + /// The associated with the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual FileStreamResult File(Stream fileStream, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing) + { + return new FileStreamResult(fileStream, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + EnableRangeProcessing = enableRangeProcessing, + }; + } + + /// + /// Returns a file in the specified (), the + /// specified as the Content-Type, and the specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// The suggested file name. + /// The of when the file was last modified. + /// The associated with the file. + /// The created for the response. + [NonAction] + public virtual FileStreamResult File(Stream fileStream, string contentType, string fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag) + { + return new FileStreamResult(fileStream, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + FileDownloadName = fileDownloadName, + }; + } + + /// + /// Returns a file in the specified (), the + /// specified as the Content-Type, and the specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// The suggested file name. + /// The of when the file was last modified. + /// The associated with the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual FileStreamResult File(Stream fileStream, string contentType, string fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing) + { + return new FileStreamResult(fileStream, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + FileDownloadName = fileDownloadName, + EnableRangeProcessing = enableRangeProcessing, + }; + } + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// The created for the response. + [NonAction] + public virtual VirtualFileResult File(string virtualPath, string contentType) + => File(virtualPath, contentType, fileDownloadName: null); + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual VirtualFileResult File(string virtualPath, string contentType, bool enableRangeProcessing) + => File(virtualPath, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing); + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + [NonAction] + public virtual VirtualFileResult File(string virtualPath, string contentType, string fileDownloadName) + => new VirtualFileResult(virtualPath, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// The suggested file name. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual VirtualFileResult File(string virtualPath, string contentType, string fileDownloadName, bool enableRangeProcessing) + => new VirtualFileResult(virtualPath, contentType) + { + FileDownloadName = fileDownloadName, + EnableRangeProcessing = enableRangeProcessing, + }; + + /// + /// Returns the file specified by (), and the + /// specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// The of when the file was last modified. + /// The associated with the file. + /// The created for the response. + [NonAction] + public virtual VirtualFileResult File(string virtualPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag) + { + return new VirtualFileResult(virtualPath, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + }; + } + + /// + /// Returns the file specified by (), and the + /// specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// The of when the file was last modified. + /// The associated with the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual VirtualFileResult File(string virtualPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing) + { + return new VirtualFileResult(virtualPath, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + EnableRangeProcessing = enableRangeProcessing, + }; + } + + /// + /// Returns the file specified by (), the + /// specified as the Content-Type, and the specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// The suggested file name. + /// The of when the file was last modified. + /// The associated with the file. + /// The created for the response. + [NonAction] + public virtual VirtualFileResult File(string virtualPath, string contentType, string fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag) + { + return new VirtualFileResult(virtualPath, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + FileDownloadName = fileDownloadName, + }; + } + + /// + /// Returns the file specified by (), the + /// specified as the Content-Type, and the specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// The suggested file name. + /// The of when the file was last modified. + /// The associated with the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual VirtualFileResult File(string virtualPath, string contentType, string fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing) + { + return new VirtualFileResult(virtualPath, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + FileDownloadName = fileDownloadName, + EnableRangeProcessing = enableRangeProcessing, + }; + } + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The path to the file. The path must be an absolute path. + /// The Content-Type of the file. + /// The created for the response. + [NonAction] + public virtual PhysicalFileResult PhysicalFile(string physicalPath, string contentType) + => PhysicalFile(physicalPath, contentType, fileDownloadName: null); + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The path to the file. The path must be an absolute path. + /// The Content-Type of the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual PhysicalFileResult PhysicalFile(string physicalPath, string contentType, bool enableRangeProcessing) + => PhysicalFile(physicalPath, contentType, fileDownloadName: null, enableRangeProcessing: enableRangeProcessing); + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The path to the file. The path must be an absolute path. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + [NonAction] + public virtual PhysicalFileResult PhysicalFile( + string physicalPath, + string contentType, + string fileDownloadName) + => new PhysicalFileResult(physicalPath, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The path to the file. The path must be an absolute path. + /// The Content-Type of the file. + /// The suggested file name. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual PhysicalFileResult PhysicalFile( + string physicalPath, + string contentType, + string fileDownloadName, + bool enableRangeProcessing) + => new PhysicalFileResult(physicalPath, contentType) + { + FileDownloadName = fileDownloadName, + EnableRangeProcessing = enableRangeProcessing, + }; + + /// + /// Returns the file specified by (), and + /// the specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The path to the file. The path must be an absolute path. + /// The Content-Type of the file. + /// The of when the file was last modified. + /// The associated with the file. + /// The created for the response. + [NonAction] + public virtual PhysicalFileResult PhysicalFile(string physicalPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag) + { + return new PhysicalFileResult(physicalPath, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + }; + } + + /// + /// Returns the file specified by (), and + /// the specified as the Content-Type. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The path to the file. The path must be an absolute path. + /// The Content-Type of the file. + /// The of when the file was last modified. + /// The associated with the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual PhysicalFileResult PhysicalFile(string physicalPath, string contentType, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing) + { + return new PhysicalFileResult(physicalPath, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + EnableRangeProcessing = enableRangeProcessing, + }; + } + + /// + /// Returns the file specified by (), the + /// specified as the Content-Type, and the specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The path to the file. The path must be an absolute path. + /// The Content-Type of the file. + /// The suggested file name. + /// The of when the file was last modified. + /// The associated with the file. + /// The created for the response. + [NonAction] + public virtual PhysicalFileResult PhysicalFile(string physicalPath, string contentType, string fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag) + { + return new PhysicalFileResult(physicalPath, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + FileDownloadName = fileDownloadName, + }; + } + + /// + /// Returns the file specified by (), the + /// specified as the Content-Type, and the specified as the suggested file name. + /// This supports range requests ( or + /// if the range is not satisfiable). + /// + /// The path to the file. The path must be an absolute path. + /// The Content-Type of the file. + /// The suggested file name. + /// The of when the file was last modified. + /// The associated with the file. + /// Set to true to enable range requests processing. + /// The created for the response. + [NonAction] + public virtual PhysicalFileResult PhysicalFile(string physicalPath, string contentType, string fileDownloadName, DateTimeOffset? lastModified, EntityTagHeaderValue entityTag, bool enableRangeProcessing) + { + return new PhysicalFileResult(physicalPath, contentType) + { + LastModified = lastModified, + EntityTag = entityTag, + FileDownloadName = fileDownloadName, + EnableRangeProcessing = enableRangeProcessing, + }; + } + + /// + /// Creates an that produces an response. + /// + /// The created for the response. + [NonAction] + public virtual UnauthorizedResult Unauthorized() + => new UnauthorizedResult(); + + /// + /// Creates an that produces a response. + /// + /// The created for the response. + [NonAction] + public virtual NotFoundResult NotFound() + => new NotFoundResult(); + + /// + /// Creates an that produces a response. + /// + /// The created for the response. + [NonAction] + public virtual NotFoundObjectResult NotFound(object value) + => new NotFoundObjectResult(value); + + /// + /// Creates an that produces a response. + /// + /// The created for the response. + [NonAction] + public virtual BadRequestResult BadRequest() + => new BadRequestResult(); + + /// + /// Creates an that produces a response. + /// + /// An error object to be returned to the client. + /// The created for the response. + [NonAction] + public virtual BadRequestObjectResult BadRequest(object error) + => new BadRequestObjectResult(error); + + /// + /// Creates an that produces a response. + /// + /// The containing errors to be returned to the client. + /// The created for the response. + [NonAction] + public virtual BadRequestObjectResult BadRequest(ModelStateDictionary modelState) + { + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + return new BadRequestObjectResult(modelState); + } + + /// + /// Creates an that produces a response. + /// + /// The created for the response. + [NonAction] + public virtual UnprocessableEntityResult UnprocessableEntity() + { + return new UnprocessableEntityResult(); + } + + /// + /// Creates an that produces a response. + /// + /// An error object to be returned to the client. + /// The created for the response. + [NonAction] + public virtual UnprocessableEntityObjectResult UnprocessableEntity(object error) + { + return new UnprocessableEntityObjectResult(error); + } + + /// + /// Creates an that produces a response. + /// + /// The containing errors to be returned to the client. + /// The created for the response. + [NonAction] + public virtual UnprocessableEntityObjectResult UnprocessableEntity(ModelStateDictionary modelState) + { + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + return new UnprocessableEntityObjectResult(modelState); + } + + /// + /// Creates an that produces a response. + /// + /// The created for the response. + [NonAction] + public virtual ConflictResult Conflict() + => new ConflictResult(); + + /// + /// Creates an that produces a response. + /// + /// Contains errors to be returned to the client. + /// The created for the response. + [NonAction] + public virtual ConflictObjectResult Conflict(object error) + => new ConflictObjectResult(error); + + /// + /// Creates an that produces a response. + /// + /// The containing errors to be returned to the client. + /// The created for the response. + [NonAction] + public virtual ConflictObjectResult Conflict(ModelStateDictionary modelState) + => new ConflictObjectResult(modelState); + + /// + /// Creates an that produces a response. + /// + /// The created for the response. + [NonAction] + public virtual ActionResult ValidationProblem(ValidationProblemDetails descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + return new BadRequestObjectResult(descriptor); + } + + /// + /// Creates an that produces a response. + /// + /// The created for the response. + [NonAction] + public virtual ActionResult ValidationProblem(ModelStateDictionary modelStateDictionary) + { + if (modelStateDictionary == null) + { + throw new ArgumentNullException(nameof(modelStateDictionary)); + } + + var validationProblem = new ValidationProblemDetails(modelStateDictionary); + return new BadRequestObjectResult(validationProblem); + } + + /// + /// Creates an that produces a response + /// with validation errors from . + /// + /// The created for the response. + [NonAction] + public virtual ActionResult ValidationProblem() + { + var validationProblem = new ValidationProblemDetails(ModelState); + return new BadRequestObjectResult(validationProblem); + } + + /// + /// Creates a object that produces a response. + /// + /// The URI at which the content has been created. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedResult Created(string uri, object value) + { + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + + return new CreatedResult(uri, value); + } + + /// + /// Creates a object that produces a response. + /// + /// The URI at which the content has been created. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedResult Created(Uri uri, object value) + { + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + + return new CreatedResult(uri, value); + } + + /// + /// Creates a object that produces a response. + /// + /// The name of the action to use for generating the URL. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedAtActionResult CreatedAtAction(string actionName, object value) + => CreatedAtAction(actionName, routeValues: null, value: value); + + /// + /// Creates a object that produces a response. + /// + /// The name of the action to use for generating the URL. + /// The route data to use for generating the URL. + /// 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) + => CreatedAtAction(actionName, controllerName: null, routeValues: routeValues, value: value); + + /// + /// Creates a object that produces a response. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedAtActionResult CreatedAtAction( + string actionName, + string controllerName, + object routeValues, + object value) + => new CreatedAtActionResult(actionName, controllerName, routeValues, value); + + /// + /// Creates a object that produces a response. + /// + /// The name of the route to use for generating the URL. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object value) + => CreatedAtRoute(routeName, routeValues: null, value: value); + + /// + /// Creates a object that produces a response. + /// + /// The route data to use for generating the URL. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedAtRouteResult CreatedAtRoute(object routeValues, object value) + => CreatedAtRoute(routeName: null, routeValues: routeValues, value: value); + + /// + /// Creates a object that produces a response. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// 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) + => new CreatedAtRouteResult(routeName, routeValues, value); + + /// + /// Creates a object that produces an response. + /// + /// The created for the response. + [NonAction] + public virtual AcceptedResult Accepted() + => new AcceptedResult(); + + /// + /// Creates a object that produces an response. + /// + /// 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) + => new AcceptedResult(location: null, value: value); + + /// + /// Creates a object that produces an response. + /// + /// The optional URI with the location at which the status of requested content can be monitored. + /// May be null. + /// The created for the response. + [NonAction] + public virtual AcceptedResult Accepted(Uri uri) + { + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + + return new AcceptedResult(locationUri: uri, value: null); + } + + /// + /// Creates a object that produces an response. + /// + /// The optional URI with the location at which the status of requested content can be monitored. + /// May be null. + /// The created for the response. + [NonAction] + public virtual AcceptedResult Accepted(string uri) + => new AcceptedResult(location: uri, value: null); + + /// + /// Creates a object that produces an response. + /// + /// The URI with the location at which the status of requested content can be monitored. + /// 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) + => new AcceptedResult(uri, value); + + /// + /// Creates a object that produces an response. + /// + /// The URI with the location at which the status of requested content can be monitored. + /// 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) + { + if (uri == null) + { + throw new ArgumentNullException(nameof(uri)); + } + + return new AcceptedResult(locationUri: uri, value: value); + } + + /// + /// Creates a object that produces an response. + /// + /// The name of the action to use for generating the URL. + /// The created for the response. + [NonAction] + public virtual AcceptedAtActionResult AcceptedAtAction(string actionName) + => AcceptedAtAction(actionName, routeValues: null, value: null); + + /// + /// Creates a object that produces an response. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The created for the response. + [NonAction] + public virtual AcceptedAtActionResult AcceptedAtAction(string actionName, string controllerName) + => AcceptedAtAction(actionName, controllerName, routeValues: null, value: null); + + /// + /// Creates a object that produces an response. + /// + /// The name of the action to use for generating the URL. + /// 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) + => AcceptedAtAction(actionName, routeValues: null, value: value); + + /// + /// Creates a object that produces an response. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// 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) + => AcceptedAtAction(actionName, controllerName, routeValues, value: null); + + /// + /// Creates a object that produces an response. + /// + /// The name of the action to use for generating the URL. + /// The route data to use for generating the URL. + /// 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) + => AcceptedAtAction(actionName, controllerName: null, routeValues: routeValues, value: value); + + /// + /// Creates a object that produces an response. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + /// 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, + string controllerName, + object routeValues, + object value) + => new AcceptedAtActionResult(actionName, controllerName, routeValues, value); + + /// + /// Creates a object that produces an response. + /// + /// The route data to use for generating the URL. + /// The created for the response. + [NonAction] + public virtual AcceptedAtRouteResult AcceptedAtRoute(object routeValues) + => AcceptedAtRoute(routeName: null, routeValues: routeValues, value: null); + + /// + /// Creates a object that produces an response. + /// + /// The name of the route to use for generating the URL. + /// The created for the response. + [NonAction] + public virtual AcceptedAtRouteResult AcceptedAtRoute(string routeName) + => AcceptedAtRoute(routeName, routeValues: null, value: null); + + /// + /// Creates a object that produces an response. + /// + /// The name of the route to use for generating the URL. + ///The route data to use for generating the URL. + /// The created for the response. + [NonAction] + public virtual AcceptedAtRouteResult AcceptedAtRoute(string routeName, object routeValues) + => AcceptedAtRoute(routeName, routeValues, value: null); + + /// + /// Creates a object that produces an response. + /// + /// The route data to use for generating the URL. + /// 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) + => AcceptedAtRoute(routeName: null, routeValues: routeValues, value: value); + + /// + /// Creates a object that produces an response. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// 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) + => new AcceptedAtRouteResult(routeName, routeValues, value); + + /// + /// Creates a . + /// + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + [NonAction] + public virtual ChallengeResult Challenge() + => new ChallengeResult(); + + /// + /// Creates a with the specified authentication schemes. + /// + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + [NonAction] + public virtual ChallengeResult Challenge(params string[] authenticationSchemes) + => new ChallengeResult(authenticationSchemes); + + /// + /// Creates a with the specified . + /// + /// used to perform the authentication + /// challenge. + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + [NonAction] + public virtual ChallengeResult Challenge(AuthenticationProperties properties) + => new ChallengeResult(properties); + + /// + /// Creates a with the specified authentication schemes and + /// . + /// + /// used to perform the authentication + /// challenge. + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + [NonAction] + public virtual ChallengeResult Challenge( + AuthenticationProperties properties, + params string[] authenticationSchemes) + => new ChallengeResult(authenticationSchemes, properties); + + /// + /// Creates a ( by default). + /// + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + [NonAction] + public virtual ForbidResult Forbid() + => new ForbidResult(); + + /// + /// Creates a ( by default) with the + /// specified authentication schemes. + /// + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + [NonAction] + public virtual ForbidResult Forbid(params string[] authenticationSchemes) + => new ForbidResult(authenticationSchemes); + + /// + /// Creates a ( by default) with the + /// specified . + /// + /// used to perform the authentication + /// challenge. + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + [NonAction] + public virtual ForbidResult Forbid(AuthenticationProperties properties) + => new ForbidResult(properties); + + /// + /// Creates a ( by default) with the + /// specified authentication schemes and . + /// + /// used to perform the authentication + /// challenge. + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + [NonAction] + public virtual ForbidResult Forbid(AuthenticationProperties properties, params string[] authenticationSchemes) + => new ForbidResult(authenticationSchemes, properties); + + /// + /// Creates a with the specified authentication scheme. + /// + /// The containing the user claims. + /// The authentication scheme to use for the sign-in operation. + /// The created for the response. + [NonAction] + public virtual SignInResult SignIn(ClaimsPrincipal principal, string authenticationScheme) + => new SignInResult(authenticationScheme, principal); + + /// + /// Creates a with the specified authentication scheme and + /// . + /// + /// The containing the user claims. + /// used to perform the sign-in operation. + /// The authentication scheme to use for the sign-in operation. + /// The created for the response. + [NonAction] + public virtual SignInResult SignIn( + ClaimsPrincipal principal, + AuthenticationProperties properties, + string authenticationScheme) + => new SignInResult(authenticationScheme, principal, properties); + + /// + /// Creates a with the specified authentication schemes. + /// + /// The authentication schemes to use for the sign-out operation. + /// The created for the response. + [NonAction] + public virtual SignOutResult SignOut(params string[] authenticationSchemes) + => new SignOutResult(authenticationSchemes); + + /// + /// Creates a with the specified authentication schemes and + /// . + /// + /// used to perform the sign-out operation. + /// The authentication scheme to use for the sign-out operation. + /// The created for the response. + [NonAction] + public virtual SignOutResult SignOut(AuthenticationProperties properties, params string[] authenticationSchemes) + => new SignOutResult(authenticationSchemes, properties); + + /// + /// Updates the specified instance using values from the controller's current + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// A that on completion returns true if the update is successful. + [NonAction] + public virtual Task TryUpdateModelAsync( + TModel model) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + return TryUpdateModelAsync(model, prefix: string.Empty); + } + + /// + /// Updates the specified instance using values from the controller's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// A that on completion returns true if the update is successful. + [NonAction] + public virtual async Task TryUpdateModelAsync( + TModel model, + string prefix) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + var valueProvider = await CompositeValueProvider.CreateAsync(ControllerContext); + return await TryUpdateModelAsync(model, prefix, valueProvider); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// A that on completion returns true if the update is successful. + [NonAction] + public virtual Task TryUpdateModelAsync( + TModel model, + string prefix, + IValueProvider valueProvider) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + prefix, + ControllerContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator); + } + + /// + /// Updates the specified instance using values from the controller's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// (s) which represent top-level properties + /// which need to be included for the current model. + /// A that on completion returns true if the update is successful. + [NonAction] + public async Task TryUpdateModelAsync( + TModel model, + string prefix, + params Expression>[] includeExpressions) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (includeExpressions == null) + { + throw new ArgumentNullException(nameof(includeExpressions)); + } + + var valueProvider = await CompositeValueProvider.CreateAsync(ControllerContext); + return await ModelBindingHelper.TryUpdateModelAsync( + model, + prefix, + ControllerContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + includeExpressions); + } + + /// + /// Updates the specified instance using values from the controller's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + [NonAction] + public async Task TryUpdateModelAsync( + TModel model, + string prefix, + Func propertyFilter) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (propertyFilter == null) + { + throw new ArgumentNullException(nameof(propertyFilter)); + } + + var valueProvider = await CompositeValueProvider.CreateAsync(ControllerContext); + return await ModelBindingHelper.TryUpdateModelAsync( + model, + prefix, + ControllerContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + propertyFilter); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// (s) which represent top-level properties + /// which need to be included for the current model. + /// A that on completion returns true if the update is successful. + [NonAction] + public Task TryUpdateModelAsync( + TModel model, + string prefix, + IValueProvider valueProvider, + params Expression>[] includeExpressions) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (includeExpressions == null) + { + throw new ArgumentNullException(nameof(includeExpressions)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + prefix, + ControllerContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + includeExpressions); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + [NonAction] + public Task TryUpdateModelAsync( + TModel model, + string prefix, + IValueProvider valueProvider, + Func propertyFilter) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (propertyFilter == null) + { + throw new ArgumentNullException(nameof(propertyFilter)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + prefix, + ControllerContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + propertyFilter); + } + + /// + /// Updates the specified instance using values from the controller's current + /// and a . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// A that on completion returns true if the update is successful. + [NonAction] + public virtual async Task TryUpdateModelAsync( + object model, + Type modelType, + string prefix) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + var valueProvider = await CompositeValueProvider.CreateAsync(ControllerContext); + return await ModelBindingHelper.TryUpdateModelAsync( + model, + modelType, + prefix, + ControllerContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + [NonAction] + public Task TryUpdateModelAsync( + object model, + Type modelType, + string prefix, + IValueProvider valueProvider, + Func propertyFilter) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (propertyFilter == null) + { + throw new ArgumentNullException(nameof(propertyFilter)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + modelType, + prefix, + ControllerContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + propertyFilter); + } + + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// true if the is valid; false otherwise. + [NonAction] + public virtual bool TryValidateModel( + object model) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + return TryValidateModel(model, prefix: null); + } + + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// The key to use when looking up information in . + /// + /// true if the is valid;false otherwise. + [NonAction] + public virtual bool TryValidateModel( + object model, + string prefix) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + ObjectValidator.Validate( + ControllerContext, + validationState: null, + prefix: prefix ?? string.Empty, + model: model); + return ModelState.IsValid; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerContext.cs new file mode 100644 index 0000000000..abb20bd102 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerContext.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// The context associated with the current request for a controller. + /// + public class ControllerContext : ActionContext + { + private IList _valueProviderFactories; + + /// + /// Creates a new . + /// + /// + /// The default constructor is provided for unit test purposes only. + /// + public ControllerContext() + { + } + + /// + /// Creates a new . + /// + /// The associated with the current request. + public ControllerContext(ActionContext context) + : base(context) + { + if (!(context.ActionDescriptor is ControllerActionDescriptor)) + { + throw new ArgumentException(Resources.FormatActionDescriptorMustBeBasedOnControllerAction( + typeof(ControllerActionDescriptor)), + nameof(context)); + } + } + + /// + /// Gets or sets the associated with the current request. + /// + public new ControllerActionDescriptor ActionDescriptor + { + get { return (ControllerActionDescriptor)base.ActionDescriptor; } + set { base.ActionDescriptor = value; } + } + + /// + /// Gets or sets the list of instances for the current request. + /// + public virtual IList ValueProviderFactories + { + get + { + if (_valueProviderFactories == null) + { + _valueProviderFactories = new List(); + } + + return _valueProviderFactories; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _valueProviderFactories = value; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerContextAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerContextAttribute.cs new file mode 100644 index 0000000000..d2210420ba --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerContextAttribute.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 System; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies that a controller property should be set with the current + /// when creating the controller. The property must have a public + /// set method. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class ControllerContextAttribute : Attribute + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActionDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActionDescriptor.cs new file mode 100644 index 0000000000..3cee82963a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActionDescriptor.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 System.Diagnostics; +using System.Globalization; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + [DebuggerDisplay("{DisplayName}")] + public class ControllerActionDescriptor : ActionDescriptor + { + public string ControllerName { get; set; } + + public virtual string ActionName { get; set; } + + public MethodInfo MethodInfo { get; set; } + + public TypeInfo ControllerTypeInfo { get; set; } + + public override string DisplayName + { + get + { + if (base.DisplayName == null && ControllerTypeInfo != null && MethodInfo != null) + { + base.DisplayName = string.Format( + CultureInfo.InvariantCulture, + "{0}.{1} ({2})", + TypeNameHelper.GetTypeDisplayName(ControllerTypeInfo), + MethodInfo.Name, + ControllerTypeInfo.Assembly.GetName().Name); + } + + return base.DisplayName; + } + + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + base.DisplayName = value; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActivatorProvider.cs new file mode 100644 index 0000000000..47375242c9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActivatorProvider.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.Reflection; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// Provides methods to create an MVC controller. + /// + public class ControllerActivatorProvider : IControllerActivatorProvider + { + private static readonly Action _dispose = Dispose; + private readonly Func _controllerActivatorCreate; + private readonly Action _controllerActivatorRelease; + + public ControllerActivatorProvider(IControllerActivator controllerActivator) + { + if (controllerActivator == null) + { + throw new ArgumentNullException(nameof(controllerActivator)); + } + + // Compat: Delegate to controllerActivator if it's not the default implementation. + if (controllerActivator.GetType() != typeof(DefaultControllerActivator)) + { + _controllerActivatorCreate = controllerActivator.Create; + _controllerActivatorRelease = controllerActivator.Release; + } + } + + public Func CreateActivator(ControllerActionDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + var controllerType = descriptor.ControllerTypeInfo?.AsType(); + if (controllerType == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(descriptor.ControllerTypeInfo), + nameof(descriptor)), + nameof(descriptor)); + } + + if (_controllerActivatorCreate != null) + { + return _controllerActivatorCreate; + } + + var typeActivator = ActivatorUtilities.CreateFactory(controllerType, Type.EmptyTypes); + return controllerContext => typeActivator(controllerContext.HttpContext.RequestServices, arguments: null); + } + + public Action CreateReleaser(ControllerActionDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + if (_controllerActivatorRelease != null) + { + return _controllerActivatorRelease; + } + + if (typeof(IDisposable).GetTypeInfo().IsAssignableFrom(descriptor.ControllerTypeInfo)) + { + return _dispose; + } + + return null; + } + + private static void Dispose(ControllerContext context, object controller) + { + if (controller == null) + { + throw new ArgumentNullException(nameof(controller)); + } + + ((IDisposable)controller).Dispose(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerBoundPropertyDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerBoundPropertyDescriptor.cs new file mode 100644 index 0000000000..f150169fd3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerBoundPropertyDescriptor.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.Reflection; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Internal; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// A descriptor for model bound properties of a controller. + /// + public class ControllerBoundPropertyDescriptor : ParameterDescriptor, IPropertyInfoParameterDescriptor + { + /// + /// Gets or sets the for this property. + /// + public PropertyInfo PropertyInfo { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFactoryProvider.cs new file mode 100644 index 0000000000..e11ad85096 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFactoryProvider.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; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + public class ControllerFactoryProvider : IControllerFactoryProvider + { + private readonly IControllerActivatorProvider _activatorProvider; + private readonly Func _factoryCreateController; + private readonly Action _factoryReleaseController; + private readonly IControllerPropertyActivator[] _propertyActivators; + + public ControllerFactoryProvider( + IControllerActivatorProvider activatorProvider, + IControllerFactory controllerFactory, + IEnumerable propertyActivators) + { + if (activatorProvider == null) + { + throw new ArgumentNullException(nameof(activatorProvider)); + } + + if (controllerFactory == null) + { + throw new ArgumentNullException(nameof(controllerFactory)); + } + + _activatorProvider = activatorProvider; + + // Compat: Delegate to the IControllerFactory if it's not the default implementation. + if (controllerFactory.GetType() != typeof(DefaultControllerFactory)) + { + _factoryCreateController = controllerFactory.CreateController; + _factoryReleaseController = controllerFactory.ReleaseController; + } + + _propertyActivators = propertyActivators.ToArray(); + } + + public Func CreateControllerFactory(ControllerActionDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + var controllerType = descriptor.ControllerTypeInfo?.AsType(); + if (controllerType == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(descriptor.ControllerTypeInfo), + nameof(descriptor)), + nameof(descriptor)); + } + + if (_factoryCreateController != null) + { + return _factoryCreateController; + } + + var controllerActivator = _activatorProvider.CreateActivator(descriptor); + var propertyActivators = GetPropertiesToActivate(descriptor); + object CreateController(ControllerContext controllerContext) + { + var controller = controllerActivator(controllerContext); + for (var i = 0; i < propertyActivators.Length; i++) + { + var propertyActivator = propertyActivators[i]; + propertyActivator(controllerContext, controller); + } + + return controller; + } + + return CreateController; + } + + public Action CreateControllerReleaser(ControllerActionDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + var controllerType = descriptor.ControllerTypeInfo?.AsType(); + if (controllerType == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(descriptor.ControllerTypeInfo), + nameof(descriptor)), + nameof(descriptor)); + } + + if (_factoryReleaseController != null) + { + return _factoryReleaseController; + } + + return _activatorProvider.CreateReleaser(descriptor); + } + + private Action[] GetPropertiesToActivate(ControllerActionDescriptor actionDescriptor) + { + var propertyActivators = new Action[_propertyActivators.Length]; + for (var i = 0; i < _propertyActivators.Length; i++) + { + var activatorProvider = _propertyActivators[i]; + propertyActivators[i] = activatorProvider.GetActivatorDelegate(actionDescriptor); + } + + return propertyActivators; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeature.cs new file mode 100644 index 0000000000..e3c8eaafc5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeature.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.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// The list of controllers types in an MVC application. The can be populated + /// using the that is available during startup at + /// and or at a later stage by requiring the + /// as a dependency in a component. + /// + public class ControllerFeature + { + /// + /// Gets the list of controller types in an MVC application. + /// + public IList Controllers { get; } = new List(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs new file mode 100644 index 0000000000..6b5a3616d2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET 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.Mvc.ApplicationParts; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// Discovers controllers from a list of instances. + /// + public class ControllerFeatureProvider : IApplicationFeatureProvider + { + private const string ControllerTypeNameSuffix = "Controller"; + + /// + public void PopulateFeature( + IEnumerable parts, + ControllerFeature feature) + { + foreach (var part in parts.OfType()) + { + foreach (var type in part.Types) + { + if (IsController(type) && !feature.Controllers.Contains(type)) + { + feature.Controllers.Add(type); + } + } + } + } + + /// + /// Determines if a given is a controller. + /// + /// The candidate. + /// true if the type is a controller; otherwise false. + protected virtual bool IsController(TypeInfo typeInfo) + { + if (!typeInfo.IsClass) + { + return false; + } + + if (typeInfo.IsAbstract) + { + return false; + } + + // We only consider public top-level classes as controllers. IsPublic returns false for nested + // classes, regardless of visibility modifiers + if (!typeInfo.IsPublic) + { + return false; + } + + if (typeInfo.ContainsGenericParameters) + { + return false; + } + + if (typeInfo.IsDefined(typeof(NonControllerAttribute))) + { + return false; + } + + if (!typeInfo.Name.EndsWith(ControllerTypeNameSuffix, StringComparison.OrdinalIgnoreCase) && + !typeInfo.IsDefined(typeof(ControllerAttribute))) + { + return false; + } + + return true; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerParameterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerParameterDescriptor.cs new file mode 100644 index 0000000000..e52cf741fb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerParameterDescriptor.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.Reflection; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Internal; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// A descriptor for method parameters of an action method. + /// + public class ControllerParameterDescriptor : ParameterDescriptor, IParameterInfoParameterDescriptor + { + /// + /// Gets or sets the . + /// + public ParameterInfo ParameterInfo { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerActivator.cs new file mode 100644 index 0000000000..8d53e715e3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerActivator.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET 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.Internal; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// that uses type activation to create controllers. + /// + public class DefaultControllerActivator : IControllerActivator + { + private readonly ITypeActivatorCache _typeActivatorCache; + + /// + /// Creates a new . + /// + /// The . + public DefaultControllerActivator(ITypeActivatorCache typeActivatorCache) + { + if (typeActivatorCache == null) + { + throw new ArgumentNullException(nameof(typeActivatorCache)); + } + + _typeActivatorCache = typeActivatorCache; + } + + /// + public virtual object Create(ControllerContext controllerContext) + { + if (controllerContext == null) + { + throw new ArgumentNullException(nameof(controllerContext)); + } + + if (controllerContext.ActionDescriptor == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(ControllerContext.ActionDescriptor), + nameof(ControllerContext))); + } + + var controllerTypeInfo = controllerContext.ActionDescriptor.ControllerTypeInfo; + + if (controllerTypeInfo == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(controllerContext.ActionDescriptor.ControllerTypeInfo), + nameof(ControllerContext.ActionDescriptor))); + } + + var serviceProvider = controllerContext.HttpContext.RequestServices; + return _typeActivatorCache.CreateInstance(serviceProvider, controllerTypeInfo.AsType()); + } + + /// + public virtual void Release(ControllerContext context, object controller) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (controller == null) + { + throw new ArgumentNullException(nameof(controller)); + } + + var disposable = controller as IDisposable; + if (disposable != null) + { + disposable.Dispose(); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs new file mode 100644 index 0000000000..3a2627d1ec --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs @@ -0,0 +1,93 @@ +// Copyright (c) .NET 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.Internal; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// Default implementation for . + /// + public class DefaultControllerFactory : IControllerFactory + { + private readonly IControllerActivator _controllerActivator; + private readonly IControllerPropertyActivator[] _propertyActivators; + + /// + /// Initializes a new instance of . + /// + /// + /// used to create controller instances. + /// + /// + /// A set of instances used to initialize controller + /// properties. + /// + public DefaultControllerFactory( + IControllerActivator controllerActivator, + IEnumerable propertyActivators) + { + if (controllerActivator == null) + { + throw new ArgumentNullException(nameof(controllerActivator)); + } + + if (propertyActivators == null) + { + throw new ArgumentNullException(nameof(propertyActivators)); + } + + _controllerActivator = controllerActivator; + _propertyActivators = propertyActivators.ToArray(); + } + + /// + /// The used to create a controller. + /// + protected IControllerActivator ControllerActivator => _controllerActivator; + + /// + public virtual object CreateController(ControllerContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.ActionDescriptor == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(ControllerContext.ActionDescriptor), + nameof(ControllerContext))); + } + + var controller = _controllerActivator.Create(context); + foreach (var propertyActivator in _propertyActivators) + { + propertyActivator.Activate(context, controller); + } + + return controller; + } + + /// + public virtual void ReleaseController(ControllerContext context, object controller) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (controller == null) + { + throw new ArgumentNullException(nameof(controller)); + } + + _controllerActivator.Release(context, controller); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivator.cs new file mode 100644 index 0000000000..87a4926be6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivator.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. + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// Provides methods to create a controller. + /// + public interface IControllerActivator + { + /// + /// Creates a controller. + /// + /// The for the executing action. + object Create(ControllerContext context); + + /// + /// Releases a controller. + /// + /// The for the executing action. + /// The controller to release. + void Release(ControllerContext context, object controller); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivatorProvider.cs new file mode 100644 index 0000000000..0ff5d2b134 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivatorProvider.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.Controllers +{ + /// + /// Provides methods to create a MVC controller. + /// + public interface IControllerActivatorProvider + { + /// + /// Creates a that creates a controller. + /// + /// The . + /// The delegate used to activate the controller. + Func CreateActivator(ControllerActionDescriptor descriptor); + + /// + /// Creates an that releases a controller. + /// + /// The . + /// The delegate used to dispose the activated controller. + Action CreateReleaser(ControllerActionDescriptor descriptor); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactory.cs new file mode 100644 index 0000000000..ae66e446a7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactory.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. + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// Provides methods for creation and disposal of controllers. + /// + public interface IControllerFactory + { + /// + /// Creates a new controller for the specified . + /// + /// for the action to execute. + /// The controller. + object CreateController(ControllerContext context); + + /// + /// Releases a controller instance. + /// + /// for the executing action. + /// The controller. + void ReleaseController(ControllerContext context, object controller); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactoryProvider.cs new file mode 100644 index 0000000000..4483047d56 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactoryProvider.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.Controllers +{ + /// + /// Provides methods to create and release a controller. + /// + public interface IControllerFactoryProvider + { + /// + /// Creates a factory for producing controllers for the specified . + /// + /// The . + /// The controller factory. + Func CreateControllerFactory(ControllerActionDescriptor descriptor); + + /// + /// Releases a controller. + /// + /// The . + /// The delegate used to release the created controller. + Action CreateControllerReleaser(ControllerActionDescriptor descriptor); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ServiceBasedControllerActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ServiceBasedControllerActivator.cs new file mode 100644 index 0000000000..663a31daa0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ServiceBasedControllerActivator.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 System; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Controllers +{ + /// + /// A that retrieves controllers as services from the request's + /// . + /// + public class ServiceBasedControllerActivator : IControllerActivator + { + /// + public object Create(ControllerContext actionContext) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + var controllerType = actionContext.ActionDescriptor.ControllerTypeInfo.AsType(); + + return actionContext.HttpContext.RequestServices.GetRequiredService(controllerType); + } + + /// + public virtual void Release(ControllerContext context, object controller) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs new file mode 100644 index 0000000000..6ed312a374 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs @@ -0,0 +1,94 @@ +// Copyright (c) .NET 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.Core; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that returns a Created (201) response with a Location header. + /// + public class CreatedAtActionResult : ObjectResult + { + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to format in the entity body. + public CreatedAtActionResult( + string actionName, + string controllerName, + object routeValues, + object value) + : base(value) + { + ActionName = actionName; + ControllerName = controllerName; + RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); + StatusCode = StatusCodes.Status201Created; + } + + /// + /// Gets or sets the used to generate URLs. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + /// Gets or sets the name of the action to use for generating the URL. + /// + public string ActionName { get; set; } + + /// + /// Gets or sets the name of the controller to use for generating the URL. + /// + public string ControllerName { get; set; } + + /// + /// Gets or sets the route data to use for generating the URL. + /// + public RouteValueDictionary RouteValues { get; set; } + + /// + public override void OnFormatting(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + base.OnFormatting(context); + + var request = context.HttpContext.Request; + + var urlHelper = UrlHelper; + if (urlHelper == null) + { + var services = context.HttpContext.RequestServices; + urlHelper = services.GetRequiredService().GetUrlHelper(context); + } + + var url = urlHelper.Action( + ActionName, + ControllerName, + RouteValues, + request.Scheme, + request.Host.ToUriComponent()); + + if (string.IsNullOrEmpty(url)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + context.HttpContext.Response.Headers[HeaderNames.Location] = url; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs new file mode 100644 index 0000000000..6e26789563 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that returns a Created (201) response with a Location header. + /// + public class CreatedAtRouteResult : ObjectResult + { + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The route data to use for generating the URL. + /// The value to format in the entity body. + public CreatedAtRouteResult(object routeValues, object value) + : this(routeName: null, routeValues: routeValues, value: value) + { + } + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to format in the entity body. + public CreatedAtRouteResult( + string routeName, + object routeValues, + object value) + : base(value) + { + RouteName = routeName; + RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); + StatusCode = StatusCodes.Status201Created; + } + + /// + /// Gets or sets the used to generate URLs. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + /// Gets or sets the name of the route to use for generating the URL. + /// + public string RouteName { get; set; } + + /// + /// Gets or sets the route data to use for generating the URL. + /// + public RouteValueDictionary RouteValues { get; set; } + + /// + public override void OnFormatting(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + base.OnFormatting(context); + + var urlHelper = UrlHelper; + if (urlHelper == null) + { + var services = context.HttpContext.RequestServices; + urlHelper = services.GetRequiredService().GetUrlHelper(context); + } + + var url = urlHelper.Link(RouteName, RouteValues); + + if (string.IsNullOrEmpty(url)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + context.HttpContext.Response.Headers[HeaderNames.Location] = url; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedResult.cs new file mode 100644 index 0000000000..5e1d23a628 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedResult.cs @@ -0,0 +1,91 @@ +// Copyright (c) .NET 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.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that returns a Created (201) response with a Location header. + /// + public class CreatedResult : ObjectResult + { + private string _location; + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The location at which the content has been created. + /// The value to format in the entity body. + public CreatedResult(string location, object value) + : base(value) + { + if (location == null) + { + throw new ArgumentNullException(nameof(location)); + } + + Location = location; + StatusCode = StatusCodes.Status201Created; + } + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The location at which the content has been created. + /// The value to format in the entity body. + public CreatedResult(Uri location, object value) + : base(value) + { + if (location == null) + { + throw new ArgumentNullException(nameof(location)); + } + + if (location.IsAbsoluteUri) + { + Location = location.AbsoluteUri; + } + else + { + Location = location.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); + } + + StatusCode = StatusCodes.Status201Created; + } + + /// + /// Gets or sets the location at which the content has been created. + /// + public string Location + { + get => _location; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _location = value; + } + } + + /// + public override void OnFormatting(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + base.OnFormatting(context); + + context.HttpContext.Response.Headers[HeaderNames.Location] = Location; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/ApplicationModelConventionExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/ApplicationModelConventionExtensions.cs new file mode 100644 index 0000000000..15f25d474c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/ApplicationModelConventionExtensions.cs @@ -0,0 +1,286 @@ +// Copyright (c) .NET 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.ApplicationModels; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Contains the extension methods for . + /// + public static class ApplicationModelConventionExtensions + { + /// + /// Removes all application model conventions of the specified type. + /// + /// The list of s. + /// The type to remove. + public static void RemoveType(this IList list) + where TApplicationModelConvention : IApplicationModelConvention + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + RemoveType(list, typeof(TApplicationModelConvention)); + } + + /// + /// Removes all application model conventions of the specified type. + /// + /// The list of s. + /// The type to remove. + public static void RemoveType(this IList list, Type type) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + for (var i = list.Count - 1; i >= 0; i--) + { + var applicationModelConvention = list[i]; + if (applicationModelConvention.GetType() == type) + { + list.RemoveAt(i); + } + } + } + + /// + /// Adds a to all the controllers in the application. + /// + /// The list of + /// in . + /// The which needs to be + /// added. + public static void Add( + this IList conventions, + IControllerModelConvention controllerModelConvention) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (controllerModelConvention == null) + { + throw new ArgumentNullException(nameof(controllerModelConvention)); + } + + conventions.Add(new ControllerApplicationModelConvention(controllerModelConvention)); + } + + /// + /// Adds a to all the actions in the application. + /// + /// The list of + /// in . + /// The which needs to be + /// added. + public static void Add( + this IList conventions, + IActionModelConvention actionModelConvention) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (actionModelConvention == null) + { + throw new ArgumentNullException(nameof(actionModelConvention)); + } + + conventions.Add(new ActionApplicationModelConvention(actionModelConvention)); + } + + /// + /// Adds a to all the parameters in the application. + /// + /// The list of + /// in . + /// The which needs to be + /// added. + public static void Add( + this IList conventions, + IParameterModelConvention parameterModelConvention) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (parameterModelConvention == null) + { + throw new ArgumentNullException(nameof(parameterModelConvention)); + } + + conventions.Add(new ParameterApplicationModelConvention(parameterModelConvention)); + } + + /// + /// Adds a to all properties and parameters in the application. + /// + /// The list of + /// in . + /// The which needs to be + /// added. + public static void Add( + this IList conventions, + IParameterModelBaseConvention parameterModelConvention) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (parameterModelConvention == null) + { + throw new ArgumentNullException(nameof(parameterModelConvention)); + } + + conventions.Add(new ParameterBaseApplicationModelConvention(parameterModelConvention)); + } + + private class ParameterApplicationModelConvention : IApplicationModelConvention + { + private readonly IParameterModelConvention _parameterModelConvention; + + public ParameterApplicationModelConvention(IParameterModelConvention parameterModelConvention) + { + _parameterModelConvention = parameterModelConvention; + } + + /// + public void Apply(ApplicationModel application) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + // Create copies of collections of controllers, actions and parameters as users could modify + // these collections from within the convention itself. + var controllers = application.Controllers.ToArray(); + foreach (var controller in controllers) + { + var actions = controller.Actions.ToArray(); + foreach (var action in actions) + { + var parameters = action.Parameters.ToArray(); + foreach (var parameter in parameters) + { + _parameterModelConvention.Apply(parameter); + } + } + } + } + } + + private class ParameterBaseApplicationModelConvention : + IApplicationModelConvention, IParameterModelBaseConvention + { + private readonly IParameterModelBaseConvention _parameterBaseModelConvention; + + public ParameterBaseApplicationModelConvention(IParameterModelBaseConvention parameterModelBaseConvention) + { + _parameterBaseModelConvention = parameterModelBaseConvention; + } + + /// + public void Apply(ApplicationModel application) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + } + + void IParameterModelBaseConvention.Apply(ParameterModelBase parameterModel) + { + if (parameterModel == null) + { + throw new ArgumentNullException(nameof(parameterModel)); + } + + _parameterBaseModelConvention.Apply(parameterModel); + } + } + + private class ActionApplicationModelConvention : IApplicationModelConvention + { + private readonly IActionModelConvention _actionModelConvention; + + public ActionApplicationModelConvention(IActionModelConvention actionModelConvention) + { + if (actionModelConvention == null) + { + throw new ArgumentNullException(nameof(actionModelConvention)); + } + + _actionModelConvention = actionModelConvention; + } + + /// + public void Apply(ApplicationModel application) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + // Create copies of collections of controllers, actions and parameters as users could modify + // these collections from within the convention itself. + var controllers = application.Controllers.ToArray(); + foreach (var controller in controllers) + { + var actions = controller.Actions.ToArray(); + foreach (var action in actions) + { + _actionModelConvention.Apply(action); + } + } + } + } + + private class ControllerApplicationModelConvention : IApplicationModelConvention + { + private readonly IControllerModelConvention _controllerModelConvention; + + public ControllerApplicationModelConvention(IControllerModelConvention controllerConvention) + { + if (controllerConvention == null) + { + throw new ArgumentNullException(nameof(controllerConvention)); + } + + _controllerModelConvention = controllerConvention; + } + + /// + public void Apply(ApplicationModel application) + { + if (application == null) + { + throw new ArgumentNullException(nameof(application)); + } + + var controllers = application.Controllers.ToArray(); + foreach (var controller in controllers) + { + _controllerModelConvention.Apply(controller); + } + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcBuilder.cs new file mode 100644 index 0000000000..c1419690a4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcBuilder.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.ApplicationParts; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// An interface for configuring MVC services. + /// + public interface IMvcBuilder + { + /// + /// Gets the where MVC services are configured. + /// + IServiceCollection Services { get; } + + /// + /// Gets the where s + /// are configured. + /// + ApplicationPartManager PartManager { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcCoreBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcCoreBuilder.cs new file mode 100644 index 0000000000..9263ebb514 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcCoreBuilder.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.ApplicationParts; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// An interface for configuring essential MVC services. + /// + public interface IMvcCoreBuilder + { + /// + /// Gets the where essential MVC services are configured. + /// + IServiceCollection Services { get; } + + /// + /// Gets the where s + /// are configured. + /// + ApplicationPartManager PartManager { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs new file mode 100644 index 0000000000..6e1f9f9a1b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs @@ -0,0 +1,155 @@ +// Copyright (c) .NET 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; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extensions for configuring MVC using an . + /// + public static class MvcCoreMvcBuilderExtensions + { + /// + /// Registers an action to configure . + /// + /// The . + /// An . + /// The . + public static IMvcBuilder AddMvcOptions( + 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; + } + + public static IMvcBuilder AddFormatterMappings( + this IMvcBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + builder.Services.Configure((options) => setupAction(options.FormatterMappings)); + return builder; + } + + /// + /// Adds an to the list of on the + /// . + /// + /// The . + /// The of the . + /// The . + public static IMvcBuilder AddApplicationPart(this IMvcBuilder builder, Assembly assembly) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + builder.ConfigureApplicationPartManager(manager => manager.ApplicationParts.Add(new AssemblyPart(assembly))); + + return builder; + } + + /// + /// Configures the of the using + /// the given . + /// + /// The . + /// The + /// The . + public static IMvcBuilder ConfigureApplicationPartManager( + this IMvcBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + setupAction(builder.PartManager); + + return builder; + } + + /// + /// Registers discovered controllers as services in the . + /// + /// The . + /// The . + public static IMvcBuilder AddControllersAsServices(this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + var feature = new ControllerFeature(); + builder.PartManager.PopulateFeature(feature); + + foreach (var controller in feature.Controllers.Select(c => c.AsType())) + { + builder.Services.TryAddTransient(controller, controller); + } + + builder.Services.Replace(ServiceDescriptor.Transient()); + + return builder; + } + + /// + /// Sets the for ASP.NET Core MVC for the application. + /// + /// The . + /// The value to configure. + /// The . + public static IMvcBuilder SetCompatibilityVersion(this IMvcBuilder builder, CompatibilityVersion version) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Services.Configure(o => o.CompatibilityVersion = version); + return builder; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs new file mode 100644 index 0000000000..76ebe6c261 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs @@ -0,0 +1,189 @@ +// Copyright (c) .NET 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.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class MvcCoreMvcCoreBuilderExtensions + { + /// + /// Registers an action to configure . + /// + /// The . + /// An . + /// The . + public static IMvcCoreBuilder AddMvcOptions( + 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; + } + + public static IMvcCoreBuilder AddFormatterMappings(this IMvcCoreBuilder builder) + { + AddFormatterMappingsServices(builder.Services); + return builder; + } + + public static IMvcCoreBuilder AddFormatterMappings( + this IMvcCoreBuilder builder, + Action setupAction) + { + AddFormatterMappingsServices(builder.Services); + + if (setupAction != null) + { + builder.Services.Configure((options) => setupAction(options.FormatterMappings)); + } + + return builder; + } + + // Internal for testing. + internal static void AddFormatterMappingsServices(IServiceCollection services) + { + services.TryAddSingleton(); + } + + public static IMvcCoreBuilder AddAuthorization(this IMvcCoreBuilder builder) + { + AddAuthorizationServices(builder.Services); + return builder; + } + + public static IMvcCoreBuilder AddAuthorization( + this IMvcCoreBuilder builder, + Action setupAction) + { + AddAuthorizationServices(builder.Services); + + if (setupAction != null) + { + builder.Services.Configure(setupAction); + } + + return builder; + } + + // Internal for testing. + internal static void AddAuthorizationServices(IServiceCollection services) + { + services.AddAuthenticationCore(); + services.AddAuthorization(); + services.AddAuthorizationPolicyEvaluator(); + + services.TryAddEnumerable( + ServiceDescriptor.Transient()); + } + + /// + /// Registers discovered controllers as services in the . + /// + /// The . + /// The . + public static IMvcCoreBuilder AddControllersAsServices(this IMvcCoreBuilder builder) + { + var feature = new ControllerFeature(); + builder.PartManager.PopulateFeature(feature); + + foreach (var controller in feature.Controllers.Select(c => c.AsType())) + { + builder.Services.TryAddTransient(controller, controller); + } + + builder.Services.Replace(ServiceDescriptor.Transient()); + + return builder; + } + + /// + /// Adds an to the list of on the + /// . + /// + /// The . + /// The of the . + /// The . + public static IMvcCoreBuilder AddApplicationPart(this IMvcCoreBuilder builder, Assembly assembly) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + builder.ConfigureApplicationPartManager(manager => manager.ApplicationParts.Add(new AssemblyPart(assembly))); + + return builder; + } + + /// + /// Configures the of the using + /// the given . + /// + /// The . + /// The + /// The . + public static IMvcCoreBuilder ConfigureApplicationPartManager( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + setupAction(builder.PartManager); + + return builder; + } + + /// + /// Sets the for ASP.NET Core MVC for the application. + /// + /// The . + /// The value to configure. + /// The . + public static IMvcCoreBuilder SetCompatibilityVersion(this IMvcCoreBuilder builder, CompatibilityVersion version) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Services.Configure(o => o.CompatibilityVersion = version); + return builder; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs new file mode 100644 index 0000000000..c0bfe2039e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -0,0 +1,276 @@ +// Copyright (c) .NET 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.Buffers; +using System.Linq; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +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.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for setting up essential MVC services in an . + /// + public static class MvcCoreServiceCollectionExtensions + { + /// + /// Adds the minimum essential MVC services to the specified . Additional services + /// including MVC's support for authorization, formatters, and validation must be added separately using the + /// returned from this method. + /// + /// The to add services to. + /// An that can be used to further configure the MVC services. + /// + /// The approach for configuring + /// MVC is provided for experienced MVC developers who wish to have full control over the set of default services + /// registered. will register + /// the minimum set of services necessary to route requests and invoke controllers. It is not expected that any + /// application will satisfy its requirements with just a call to + /// . Additional configuration using the + /// will be required. + /// + public static IMvcCoreBuilder AddMvcCore(this IServiceCollection services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + var partManager = GetApplicationPartManager(services); + services.TryAddSingleton(partManager); + + ConfigureDefaultFeatureProviders(partManager); + ConfigureDefaultServices(services); + AddMvcCoreServices(services); + + var builder = new MvcCoreBuilder(services, partManager); + + return builder; + } + + private static void ConfigureDefaultFeatureProviders(ApplicationPartManager manager) + { + if (!manager.FeatureProviders.OfType().Any()) + { + manager.FeatureProviders.Add(new ControllerFeatureProvider()); + } + } + + private static ApplicationPartManager GetApplicationPartManager(IServiceCollection services) + { + var manager = GetServiceFromCollection(services); + if (manager == null) + { + manager = new ApplicationPartManager(); + + var environment = GetServiceFromCollection(services); + var entryAssemblyName = environment?.ApplicationName; + if (string.IsNullOrEmpty(entryAssemblyName)) + { + return manager; + } + + manager.PopulateDefaultParts(entryAssemblyName); + } + + return manager; + } + + private static T GetServiceFromCollection(IServiceCollection services) + { + return (T)services + .LastOrDefault(d => d.ServiceType == typeof(T)) + ?.ImplementationInstance; + } + + /// + /// Adds the minimum essential MVC services to the specified . Additional services + /// including MVC's support for authorization, formatters, and validation must be added separately using the + /// returned from this method. + /// + /// The to add services to. + /// An to configure the provided . + /// An that can be used to further configure the MVC services. + /// + /// The approach for configuring + /// MVC is provided for experienced MVC developers who wish to have full control over the set of default services + /// registered. will register + /// the minimum set of services necessary to route requests and invoke controllers. It is not expected that any + /// application will satisfy its requirements with just a call to + /// . Additional configuration using the + /// will be required. + /// + public static IMvcCoreBuilder AddMvcCore( + this IServiceCollection services, + Action setupAction) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + var builder = services.AddMvcCore(); + services.Configure(setupAction); + + return builder; + } + + // To enable unit testing + internal static void AddMvcCoreServices(IServiceCollection services) + { + // + // Options + // + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcCoreMvcOptionsSetup>()); + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcOptionsConfigureCompatibilityOptions>()); + services.TryAddEnumerable( + ServiceDescriptor.Transient, ApiBehaviorOptionsSetup>()); + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcCoreRouteOptionsSetup>()); + + // + // Action Discovery + // + // These are consumed only when creating action descriptors, then they can be deallocated + + services.TryAddEnumerable( + ServiceDescriptor.Transient()); + services.TryAddEnumerable( + ServiceDescriptor.Transient()); + services.TryAddEnumerable( + ServiceDescriptor.Transient()); + + services.TryAddSingleton(); + + // + // Action Selection + // + services.TryAddSingleton(); + services.TryAddSingleton(); + + // Will be cached by the DefaultActionSelector + services.TryAddEnumerable( + ServiceDescriptor.Transient()); + + // + // Controller Factory + // + // This has a cache, so it needs to be a singleton + services.TryAddSingleton(); + + // Will be cached by the DefaultControllerFactory + services.TryAddTransient(); + + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddEnumerable( + ServiceDescriptor.Transient()); + + // + // Action Invoker + // + // The IActionInvokerFactory is cachable + services.TryAddSingleton(); + services.TryAddEnumerable( + ServiceDescriptor.Transient()); + + // These are stateless + services.TryAddSingleton(); + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + services.TryAddSingleton(); + + // + // Request body limit filters + // + services.TryAddTransient(); + services.TryAddTransient(); + services.TryAddTransient(); + + // + // ModelBinding, Validation + // + // The DefaultModelMetadataProvider does significant caching and should be a singleton. + services.TryAddSingleton(); + services.TryAdd(ServiceDescriptor.Transient(s => + { + var options = s.GetRequiredService>().Value; + return new DefaultCompositeMetadataDetailsProvider(options.ModelMetadataDetailsProviders); + })); + services.TryAddSingleton(); + services.TryAddSingleton(s => + { + var options = s.GetRequiredService>().Value; + var metadataProvider = s.GetRequiredService(); + return new DefaultObjectValidator(metadataProvider, options.ModelValidatorProviders); + }); + services.TryAddSingleton(); + services.TryAddSingleton(); + + // + // Random Infrastructure + // + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(ArrayPool.Shared); + services.TryAddSingleton(ArrayPool.Shared); + services.TryAddSingleton(); + services.TryAddSingleton, ObjectResultExecutor>(); + services.TryAddSingleton, PhysicalFileResultExecutor>(); + services.TryAddSingleton, VirtualFileResultExecutor>(); + services.TryAddSingleton, FileStreamResultExecutor>(); + services.TryAddSingleton, FileContentResultExecutor>(); + services.TryAddSingleton, RedirectResultExecutor>(); + services.TryAddSingleton, LocalRedirectResultExecutor>(); + services.TryAddSingleton, RedirectToActionResultExecutor>(); + services.TryAddSingleton, RedirectToRouteResultExecutor>(); + services.TryAddSingleton, RedirectToPageResultExecutor>(); + services.TryAddSingleton, ContentResultExecutor>(); + + // + // Route Handlers + // + services.TryAddSingleton(); // Only one per app + services.TryAddTransient(); // Many per app + + // + // Middleware pipeline filter related + // + services.TryAddSingleton(); + // This maintains a cache of middleware pipelines, so it needs to be a singleton + services.TryAddSingleton(); + } + + private static void ConfigureDefaultServices(IServiceCollection services) + { + services.AddRouting(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DisableRequestSizeLimitAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DisableRequestSizeLimitAttribute.cs new file mode 100644 index 0000000000..b92092667e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DisableRequestSizeLimitAttribute.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET 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.Internal; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Disables the request body size limit. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class DisableRequestSizeLimitAttribute : Attribute, IFilterFactory, IOrderedFilter + { + /// + /// Gets the order value for determining the order of execution of filters. Filters execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Filters are executed in an ordering determined by an ascending sort of the property. + /// + /// + /// The default Order for this attribute is 900 because it must run before ValidateAntiForgeryTokenAttribute and + /// after any filter which does authentication or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400). + /// + /// + /// Look at for more detailed info. + /// + /// + public int Order { get; set; } = 900; + + /// + public bool IsReusable => true; + + /// + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + var filter = serviceProvider.GetRequiredService(); + return filter; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/EmptyResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/EmptyResult.cs new file mode 100644 index 0000000000..dbe278e7a4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/EmptyResult.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 +{ + /// + /// Represents an that when executed will + /// do nothing. + /// + public class EmptyResult : ActionResult + { + /// + public override void ExecuteResult(ActionContext context) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileContentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileContentResult.cs new file mode 100644 index 0000000000..99385c9bd3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileContentResult.cs @@ -0,0 +1,84 @@ +// Copyright (c) .NET 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.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Represents an that when executed will + /// write a binary file to the response. + /// + public class FileContentResult : FileResult + { + private byte[] _fileContents; + + /// + /// Creates a new instance with + /// the provided and the + /// provided . + /// + /// The bytes that represent the file contents. + /// The Content-Type header of the response. + public FileContentResult(byte[] fileContents, string contentType) + : this(fileContents, MediaTypeHeaderValue.Parse(contentType)) + { + if (fileContents == null) + { + throw new ArgumentNullException(nameof(fileContents)); + } + } + + /// + /// Creates a new instance with + /// the provided and the + /// provided . + /// + /// The bytes that represent the file contents. + /// The Content-Type header of the response. + public FileContentResult(byte[] fileContents, MediaTypeHeaderValue contentType) + : base(contentType?.ToString()) + { + if (fileContents == null) + { + throw new ArgumentNullException(nameof(fileContents)); + } + + FileContents = fileContents; + } + + /// + /// Gets or sets the file contents. + /// + public byte[] FileContents + { + get => _fileContents; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _fileContents = value; + } + } + + /// + public override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var executor = context.HttpContext.RequestServices.GetRequiredService>(); + return executor.ExecuteAsync(context, this); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs new file mode 100644 index 0000000000..6b3b7743f2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileResult.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 Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Represents an that when executed will + /// write a file as the response. + /// + public abstract class FileResult : ActionResult + { + private string _fileDownloadName; + + /// + /// Creates a new instance with + /// the provided . + /// + /// The Content-Type header of the response. + protected FileResult(string contentType) + { + if (contentType == null) + { + throw new ArgumentNullException(nameof(contentType)); + } + + ContentType = contentType; + } + + /// + /// Gets the Content-Type header for the response. + /// + public string ContentType { get; } + + /// + /// Gets the file name that will be used in the Content-Disposition header of the response. + /// + public string FileDownloadName + { + get { return _fileDownloadName ?? string.Empty; } + set { _fileDownloadName = value; } + } + + /// + /// Gets or sets the last modified information associated with the . + /// + public DateTimeOffset? LastModified { get; set; } + + /// + /// Gets or sets the etag associated with the . + /// + public EntityTagHeaderValue EntityTag { get; set; } + + /// + /// Gets or sets the value that enables range processing for the . + /// + public bool EnableRangeProcessing { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileStreamResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileStreamResult.cs new file mode 100644 index 0000000000..486069a5ea --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileStreamResult.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET 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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Represents an that when executed will + /// write a file from a stream to the response. + /// + public class FileStreamResult : FileResult + { + private Stream _fileStream; + + /// + /// Creates a new instance with + /// the provided and the + /// provided . + /// + /// The stream with the file. + /// The Content-Type header of the response. + public FileStreamResult(Stream fileStream, string contentType) + : this(fileStream, MediaTypeHeaderValue.Parse(contentType)) + { + } + + /// + /// Creates a new instance with + /// the provided and the + /// provided . + /// + /// The stream with the file. + /// The Content-Type header of the response. + public FileStreamResult(Stream fileStream, MediaTypeHeaderValue contentType) + : base(contentType?.ToString()) + { + if (fileStream == null) + { + throw new ArgumentNullException(nameof(fileStream)); + } + + FileStream = fileStream; + } + + /// + /// Gets or sets the stream with the file that will be sent back as the response. + /// + public Stream FileStream + { + get => _fileStream; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _fileStream = value; + } + } + + /// + public override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var executor = context.HttpContext.RequestServices.GetRequiredService>(); + return executor.ExecuteAsync(context, this); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ActionFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ActionFilterAttribute.cs new file mode 100644 index 0000000000..efb06d56ce --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ActionFilterAttribute.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; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// An abstract filter that asynchronously surrounds execution of the action and the action result. Subclasses + /// should override , or + /// but not and either of the other two. + /// Similarly subclasses should override , or + /// but not and either of the other two. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public abstract class ActionFilterAttribute : + Attribute, IActionFilter, IAsyncActionFilter, IResultFilter, IAsyncResultFilter, IOrderedFilter + { + /// + public int Order { get; set; } + + /// + public virtual void OnActionExecuting(ActionExecutingContext context) + { + } + + /// + public virtual void OnActionExecuted(ActionExecutedContext context) + { + } + + /// + public virtual async Task OnActionExecutionAsync( + ActionExecutingContext context, + ActionExecutionDelegate next) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + + OnActionExecuting(context); + if (context.Result == null) + { + OnActionExecuted(await next()); + } + } + + /// + public virtual void OnResultExecuting(ResultExecutingContext context) + { + } + + /// + public virtual void OnResultExecuted(ResultExecutedContext context) + { + } + + /// + public virtual async Task OnResultExecutionAsync( + ResultExecutingContext context, + ResultExecutionDelegate next) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + + OnResultExecuting(context); + if (!context.Cancel) + { + OnResultExecuted(await next()); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ExceptionFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ExceptionFilterAttribute.cs new file mode 100644 index 0000000000..e6efec0d2e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ExceptionFilterAttribute.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 System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// An abstract filter that runs asynchronously after an action has thrown an . Subclasses + /// must override or but not both. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public abstract class ExceptionFilterAttribute : Attribute, IAsyncExceptionFilter, IExceptionFilter, IOrderedFilter + { + /// + public int Order { get; set; } + + /// + public virtual Task OnExceptionAsync(ExceptionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + OnException(context); + return Task.CompletedTask; + } + + /// + public virtual void OnException(ExceptionContext context) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterCollection.cs new file mode 100644 index 0000000000..b5ea9a26c1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterCollection.cs @@ -0,0 +1,176 @@ +// Copyright (c) .NET 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.ObjectModel; +using Microsoft.AspNetCore.Mvc.Core; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + public class FilterCollection : Collection + { + /// + /// Adds a type representing an . + /// + /// Type representing an . + /// An representing the added type. + /// + /// Filter instances will be created using + /// . + /// Use to register a service as a filter. + /// + public IFilterMetadata Add() where TFilterType : IFilterMetadata + { + return Add(typeof(TFilterType)); + } + + /// + /// Adds a type representing an . + /// + /// Type representing an . + /// An representing the added type. + /// + /// Filter instances will be created using + /// . + /// Use to register a service as a filter. + /// + public IFilterMetadata Add(Type filterType) + { + if (filterType == null) + { + throw new ArgumentNullException(nameof(filterType)); + } + + return Add(filterType, order: 0); + } + + /// + /// Adds a type representing an . + /// + /// Type representing an . + /// The order of the added filter. + /// An representing the added type. + /// + /// Filter instances will be created using + /// . + /// Use to register a service as a filter. + /// + public IFilterMetadata Add(int order) where TFilterType : IFilterMetadata + { + return Add(typeof(TFilterType), order); + } + + /// + /// Adds a type representing an . + /// + /// Type representing an . + /// The order of the added filter. + /// An representing the added type. + /// + /// Filter instances will be created using + /// . + /// Use to register a service as a filter. + /// + public IFilterMetadata Add(Type filterType, int order) + { + if (filterType == null) + { + throw new ArgumentNullException(nameof(filterType)); + } + + if (!typeof(IFilterMetadata).IsAssignableFrom(filterType)) + { + var message = Resources.FormatTypeMustDeriveFromType( + filterType.FullName, + typeof(IFilterMetadata).FullName); + throw new ArgumentException(message, nameof(filterType)); + } + + var filter = new TypeFilterAttribute(filterType) { Order = order }; + Add(filter); + return filter; + } + + /// + /// Adds a type representing an . + /// + /// Type representing an . + /// An representing the added service type. + /// + /// Filter instances will be created through dependency injection. Use + /// to register a service that will be created via + /// type activation. + /// + public IFilterMetadata AddService() where TFilterType : IFilterMetadata + { + return AddService(typeof(TFilterType)); + } + + /// + /// Adds a type representing an . + /// + /// Type representing an . + /// An representing the added service type. + /// + /// Filter instances will be created through dependency injection. Use + /// to register a service that will be created via + /// type activation. + /// + public IFilterMetadata AddService(Type filterType) + { + if (filterType == null) + { + throw new ArgumentNullException(nameof(filterType)); + } + + return AddService(filterType, order: 0); + } + + /// + /// Adds a type representing an . + /// + /// Type representing an . + /// The order of the added filter. + /// An representing the added service type. + /// + /// Filter instances will be created through dependency injection. Use + /// to register a service that will be created via + /// type activation. + /// + public IFilterMetadata AddService(int order) where TFilterType : IFilterMetadata + { + return AddService(typeof(TFilterType), order); + } + + /// + /// Adds a type representing an . + /// + /// Type representing an . + /// The order of the added filter. + /// An representing the added service type. + /// + /// Filter instances will be created through dependency injection. Use + /// to register a service that will be created via + /// type activation. + /// + public IFilterMetadata AddService(Type filterType, int order) + { + if (filterType == null) + { + throw new ArgumentNullException(nameof(filterType)); + } + + if (!typeof(IFilterMetadata).IsAssignableFrom(filterType)) + { + var message = Resources.FormatTypeMustDeriveFromType( + filterType.FullName, + typeof(IFilterMetadata).FullName); + throw new ArgumentException(message, nameof(filterType)); + } + + var filter = new ServiceFilterAttribute(filterType) { Order = order }; + Add(filter); + return filter; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterScope.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterScope.cs new file mode 100644 index 0000000000..e687d3975c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterScope.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. + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// + /// Contains constant values for known filter scopes. + /// + /// + /// Scope defines the ordering of filters that have the same order. Scope is by-default + /// defined by how a filter is registered. + /// + /// + public static class FilterScope + { + public static readonly int First = 0; + public static readonly int Global = 10; + public static readonly int Controller = 20; + public static readonly int Action = 30; + public static readonly int Last = 100; + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/MiddlewareFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/MiddlewareFilterAttribute.cs new file mode 100644 index 0000000000..019f1551f3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/MiddlewareFilterAttribute.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; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Executes a middleware pipeline provided the by the . + /// The middleware pipeline will be treated as an async resource filter. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public class MiddlewareFilterAttribute : Attribute, IFilterFactory, IOrderedFilter + { + /// + /// Instantiates a new instance of . + /// + /// A type which configures a middleware pipeline. + public MiddlewareFilterAttribute(Type configurationType) + { + if (configurationType == null) + { + throw new ArgumentNullException(nameof(configurationType)); + } + + ConfigurationType = configurationType; + } + + public Type ConfigurationType { get; } + + /// + public int Order { get; set; } + + /// + public bool IsReusable => true; + + /// + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + var middlewarePipelineService = serviceProvider.GetRequiredService(); + var pipeline = middlewarePipelineService.GetPipeline(ConfigurationType); + + return new MiddlewareFilter(pipeline); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ResultFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ResultFilterAttribute.cs new file mode 100644 index 0000000000..b7ff680661 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ResultFilterAttribute.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 System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// An abstract filter that asynchronously surrounds execution of the action result. Subclasses + /// must override , or + /// but not and either of the other two. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public abstract class ResultFilterAttribute : Attribute, IResultFilter, IAsyncResultFilter, IOrderedFilter + { + /// + public int Order { get; set; } + + /// + public virtual void OnResultExecuting(ResultExecutingContext context) + { + } + + /// + public virtual void OnResultExecuted(ResultExecutedContext context) + { + } + + /// + public virtual async Task OnResultExecutionAsync( + ResultExecutingContext context, + ResultExecutionDelegate next) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + + OnResultExecuting(context); + if (!context.Cancel) + { + OnResultExecuted(await next()); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ForbidResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ForbidResult.cs new file mode 100644 index 0000000000..b967d74c08 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ForbidResult.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; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that on execution invokes . + /// + public class ForbidResult : ActionResult + { + /// + /// Initializes a new instance of . + /// + public ForbidResult() + : this(Array.Empty()) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication scheme. + /// + /// The authentication scheme to challenge. + public ForbidResult(string authenticationScheme) + : this(new[] { authenticationScheme }) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication schemes. + /// + /// The authentication schemes to challenge. + public ForbidResult(IList authenticationSchemes) + : this(authenticationSchemes, properties: null) + { + } + + /// + /// Initializes a new instance of with the + /// specified . + /// + /// used to perform the authentication + /// challenge. + public ForbidResult(AuthenticationProperties properties) + : this(Array.Empty(), properties) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication scheme and . + /// + /// The authentication schemes to challenge. + /// used to perform the authentication + /// challenge. + public ForbidResult(string authenticationScheme, AuthenticationProperties properties) + : this(new[] { authenticationScheme }, properties) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication schemes and . + /// + /// The authentication scheme to challenge. + /// used to perform the authentication + /// challenge. + public ForbidResult(IList authenticationSchemes, AuthenticationProperties properties) + { + AuthenticationSchemes = authenticationSchemes; + Properties = properties; + } + + /// + /// Gets or sets the authentication schemes that are challenged. + /// + public IList AuthenticationSchemes { get; set; } + + /// + /// Gets or sets the used to perform the authentication challenge. + /// + public AuthenticationProperties Properties { get; set; } + + /// + public override async Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); + var logger = loggerFactory.CreateLogger(); + + logger.ForbidResultExecuting(AuthenticationSchemes); + + if (AuthenticationSchemes != null && AuthenticationSchemes.Count > 0) + { + for (var i = 0; i < AuthenticationSchemes.Count; i++) + { + await context.HttpContext.ForbidAsync(AuthenticationSchemes[i], Properties); + } + } + else + { + await context.HttpContext.ForbidAsync(Properties); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FormatFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FormatFilterAttribute.cs new file mode 100644 index 0000000000..dab307f89d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FormatFilterAttribute.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.Formatters; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A filter that will use the format value in the route data or query string to set the content type on an + /// returned from an action. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class FormatFilterAttribute : Attribute, IFilterFactory + { + /// + public bool IsReusable => true; + + /// + /// Creates an instance of . + /// + /// The . + /// An instance of . + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + return serviceProvider.GetRequiredService(); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatFilter.cs new file mode 100644 index 0000000000..dd7b568fce --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatFilter.cs @@ -0,0 +1,194 @@ +// Copyright (c) .NET 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.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Formatters.Internal; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// A filter that will use the format value in the route data or query string to set the content type on an + /// returned from an action. + /// + public class FormatFilter : IFormatFilter, IResourceFilter, IResultFilter + { + private readonly MvcOptions _options; + private readonly ILogger _logger; + + /// + /// Initializes an instance of . + /// + /// The + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public FormatFilter(IOptions options) + : this(options, NullLoggerFactory.Instance) + { + } + + /// + /// Initializes an instance of . + /// + /// The + /// The . + public FormatFilter(IOptions options, ILoggerFactory loggerFactory) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _options = options.Value; + _logger = loggerFactory.CreateLogger(GetType()); + } + + /// + public virtual string GetFormat(ActionContext context) + { + if (context.RouteData.Values.TryGetValue("format", out var obj)) + { + // null and string.Empty are equivalent for route values. + var routeValue = obj?.ToString(); + return string.IsNullOrEmpty(routeValue) ? null : routeValue; + } + + var query = context.HttpContext.Request.Query["format"]; + if (query.Count > 0) + { + return query.ToString(); + } + + return null; + } + + /// + /// As a , this filter looks at the request and rejects it before going ahead if + /// 1. The format in the request does not match any format in the map. + /// 2. If there is a conflicting producesFilter. + /// + /// The . + public void OnResourceExecuting(ResourceExecutingContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var format = GetFormat(context); + if (format == null) + { + // no format specified by user, so the filter is muted + return; + } + + var contentType = _options.FormatterMappings.GetMediaTypeMappingForFormat(format); + if (contentType == null) + { + _logger.UnsupportedFormatFilterContentType(format); + + // no contentType exists for the format, return 404 + context.Result = new NotFoundResult(); + return; + } + + // Determine media types this action supports. + var responseTypeFilters = context.Filters.OfType(); + var supportedMediaTypes = new MediaTypeCollection(); + foreach (var filter in responseTypeFilters) + { + filter.SetContentTypes(supportedMediaTypes); + } + + // Check if support is adequate for requested media type. + if (supportedMediaTypes.Count == 0) + { + _logger.ActionDoesNotExplicitlySpecifyContentTypes(); + return; + } + + // We need to check if the action can generate the content type the user asked for. That is, treat the + // request's format and IApiResponseMetadataProvider-provided content types similarly to an Accept + // header and an output formatter's SupportedMediaTypes: Confirm action supports a more specific media + // type than requested e.g. OK if "text/*" requested and action supports "text/plain". + if (!IsSuperSetOfAnySupportedMediaType(contentType, supportedMediaTypes)) + { + _logger.ActionDoesNotSupportFormatFilterContentType(contentType, supportedMediaTypes); + context.Result = new NotFoundResult(); + } + } + + private bool IsSuperSetOfAnySupportedMediaType(string contentType, MediaTypeCollection supportedMediaTypes) + { + var parsedContentType = new MediaType(contentType); + for (var i = 0; i < supportedMediaTypes.Count; i++) + { + var supportedMediaType = new MediaType(supportedMediaTypes[i]); + if (supportedMediaType.IsSubsetOf(parsedContentType)) + { + return true; + } + } + + return false; + } + + /// + public void OnResourceExecuted(ResourceExecutedContext context) + { + } + + /// + /// Sets a Content Type on an using a format value from the request. + /// + /// The . + public void OnResultExecuting(ResultExecutingContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var format = GetFormat(context); + if (format == null) + { + // no format specified by user, so the filter is muted + return; + } + + var objectResult = context.Result as ObjectResult; + if (objectResult == null) + { + return; + } + + // If the action sets a single content type, then it takes precedence over the user + // supplied content type based on format mapping. + if ((objectResult.ContentTypes != null && objectResult.ContentTypes.Count == 1) || + !string.IsNullOrEmpty(context.HttpContext.Response.ContentType)) + { + _logger.CannotApplyFormatFilterContentType(format); + return; + } + + var contentType = _options.FormatterMappings.GetMediaTypeMappingForFormat(format); + objectResult.ContentTypes.Clear(); + objectResult.ContentTypes.Add(contentType); + } + + /// + public void OnResultExecuted(ResultExecutedContext context) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatterMappings.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatterMappings.cs new file mode 100644 index 0000000000..81613febfc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatterMappings.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 Microsoft.AspNetCore.Mvc.Core; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Used to specify mapping between the URL Format and corresponding media type. + /// + public class FormatterMappings + { + private readonly Dictionary _map = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Sets mapping for the format to specified media type. + /// If the format already exists, the media type will be overwritten with the new value. + /// + /// The format value. + /// The media type for the format value. + public void SetMediaTypeMappingForFormat(string format, string contentType) + { + if (format == null) + { + throw new ArgumentNullException(nameof(format)); + } + + if (contentType == null) + { + throw new ArgumentNullException(nameof(contentType)); + } + + SetMediaTypeMappingForFormat(format, MediaTypeHeaderValue.Parse(contentType)); + } + + /// + /// Sets mapping for the format to specified media type. + /// If the format already exists, the media type will be overwritten with the new value. + /// + /// The format value. + /// The media type for the format value. + public void SetMediaTypeMappingForFormat(string format, MediaTypeHeaderValue contentType) + { + if (format == null) + { + throw new ArgumentNullException(nameof(format)); + } + + if (contentType == null) + { + throw new ArgumentNullException(nameof(contentType)); + } + + ValidateContentType(contentType); + format = RemovePeriodIfPresent(format); + _map[format] = contentType.ToString(); + } + + /// + /// Gets the media type for the specified format. + /// + /// The format value. + /// The media type for input format. + public string GetMediaTypeMappingForFormat(string format) + { + if (string.IsNullOrEmpty(format)) + { + var message = Resources.FormatFormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat( + nameof(format)); + + throw new ArgumentException(message, nameof(format)); + } + + format = RemovePeriodIfPresent(format); + + _map.TryGetValue(format, out var value); + + return value; + } + + /// + /// Clears the media type mapping for the format. + /// + /// The format value. + /// true if the format is successfully found and cleared; otherwise, false. + public bool ClearMediaTypeMappingForFormat(string format) + { + if (format == null) + { + throw new ArgumentNullException(nameof(format)); + } + + format = RemovePeriodIfPresent(format); + return _map.Remove(format); + } + + private void ValidateContentType(MediaTypeHeaderValue contentType) + { + if (contentType.Type == "*" || contentType.SubType == "*") + { + throw new ArgumentException( + string.Format(Resources.FormatterMappings_NotValidMediaType, contentType), + nameof(contentType)); + } + } + + private string RemovePeriodIfPresent(string format) + { + if (string.IsNullOrEmpty(format)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(format)); + } + + if (format.StartsWith(".", StringComparison.Ordinal)) + { + if (format == ".") + { + throw new ArgumentException(string.Format(Resources.Format_NotValid, format), nameof(format)); + } + + format = format.Substring(1); + } + + return format; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs new file mode 100644 index 0000000000..244ffa829e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/HttpNoContentOutputFormatter.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.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Sets the status code to 204 if the content is null. + /// + public class HttpNoContentOutputFormatter : IOutputFormatter + { + /// + /// Indicates whether to select this formatter if the returned value from the action + /// is null. + /// + public bool TreatNullValueAsNoContent { get; set; } = true; + + /// + public bool CanWriteResult(OutputFormatterCanWriteContext context) + { + // ignore the contentType and just look at the content. + // This formatter will be selected if the content is null. + // We check for Task as a user can directly create an ObjectContentResult with the unwrapped type. + if (context.ObjectType == typeof(void) || context.ObjectType == typeof(Task)) + { + return true; + } + + return TreatNullValueAsNoContent && context.Object == null; + } + + /// + public Task WriteAsync(OutputFormatterWriteContext context) + { + var response = context.HttpContext.Response; + response.ContentLength = 0; + + if (response.StatusCode == StatusCodes.Status200OK) + { + response.StatusCode = StatusCodes.Status204NoContent; + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/InputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/InputFormatter.cs new file mode 100644 index 0000000000..2392b5a47c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/InputFormatter.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.Collections.Generic; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Core; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Reads an object from the request body. + /// + public abstract class InputFormatter : IInputFormatter, IApiRequestFormatMetadataProvider + { + /// + /// Gets the mutable collection of media type elements supported by + /// this . + /// + public MediaTypeCollection SupportedMediaTypes { get; } = new MediaTypeCollection(); + + /// + /// Gets the default value for a given type. Used to return a default value when the body contains no content. + /// + /// The type of the value. + /// The default value for the type. + protected virtual object GetDefaultValueForType(Type modelType) + { + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + if (modelType.GetTypeInfo().IsValueType) + { + return Activator.CreateInstance(modelType); + } + + return null; + } + + /// + public virtual bool CanRead(InputFormatterContext context) + { + if (SupportedMediaTypes.Count == 0) + { + var message = Resources.FormatFormatter_NoMediaTypes( + GetType().FullName, + nameof(SupportedMediaTypes)); + + throw new InvalidOperationException(message); + } + + if (!CanReadType(context.ModelType)) + { + return false; + } + + var contentType = context.HttpContext.Request.ContentType; + if (string.IsNullOrEmpty(contentType)) + { + return false; + } + + // Confirm the request's content type is more specific than a media type this formatter supports e.g. OK if + // client sent "text/plain" data and this formatter supports "text/*". + return IsSubsetOfAnySupportedContentType(contentType); + } + + private bool IsSubsetOfAnySupportedContentType(string contentType) + { + var parsedContentType = new MediaType(contentType); + for (var i = 0; i < SupportedMediaTypes.Count; i++) + { + var supportedMediaType = new MediaType(SupportedMediaTypes[i]); + if (parsedContentType.IsSubsetOf(supportedMediaType)) + { + return true; + } + } + return false; + } + + /// + /// Determines whether this can deserialize an object of the given + /// . + /// + /// The of object that will be read. + /// true if the can be read, otherwise false. + protected virtual bool CanReadType(Type type) + { + return true; + } + + /// + public virtual Task ReadAsync(InputFormatterContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var request = context.HttpContext.Request; + if (request.ContentLength == 0) + { + if (context.TreatEmptyInputAsDefaultValue) + { + return InputFormatterResult.SuccessAsync(GetDefaultValueForType(context.ModelType)); + } + + return InputFormatterResult.NoValueAsync(); + } + + return ReadRequestBodyAsync(context); + } + + /// + /// Reads an object from the request body. + /// + /// The . + /// A that on completion deserializes the request body. + public abstract Task ReadRequestBodyAsync(InputFormatterContext context); + + /// + public virtual IReadOnlyList GetSupportedContentTypes(string contentType, Type objectType) + { + if (SupportedMediaTypes.Count == 0) + { + var message = Resources.FormatFormatter_NoMediaTypes( + GetType().FullName, + nameof(SupportedMediaTypes)); + + throw new InvalidOperationException(message); + } + + if (!CanReadType(objectType)) + { + return null; + } + + if (contentType == null) + { + // If contentType is null, then any type we support is valid. + return SupportedMediaTypes; + } + else + { + var parsedContentType = new MediaType(contentType); + List mediaTypes = null; + + // Confirm this formatter supports a more specific media type than requested e.g. OK if "text/*" + // requested and formatter supports "text/plain". Treat contentType like it came from an Content-Type header. + foreach (var mediaType in SupportedMediaTypes) + { + var parsedMediaType = new MediaType(mediaType); + if (parsedMediaType.IsSubsetOf(parsedContentType)) + { + if (mediaTypes == null) + { + mediaTypes = new List(); + } + + mediaTypes.Add(mediaType); + } + } + + return mediaTypes; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs new file mode 100644 index 0000000000..e72fccac65 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs @@ -0,0 +1,707 @@ +// Copyright (c) .NET 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.Text; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Formatters.Internal; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// A media type value. + /// + public struct MediaType + { + private static readonly StringSegment QualityParameter = new StringSegment("q"); + + private readonly MediaTypeParameterParser _parameterParser; + + /// + /// Initializes a instance. + /// + /// The with the media type. + public MediaType(string mediaType) + : this(mediaType, 0, mediaType.Length) + { + } + + /// + /// Initializes a instance. + /// + /// The with the media type. + public MediaType(StringSegment mediaType) + : this(mediaType.Buffer, mediaType.Offset, mediaType.Length) + { + } + + /// + /// Initializes a instance. + /// + /// The with the media type. + /// The offset in the where the parsing starts. + /// The length of the media type to parse if provided. + public MediaType(string mediaType, int offset, int? length) + { + if (mediaType == null) + { + throw new ArgumentNullException(nameof(mediaType)); + } + + if (offset < 0 || offset >= mediaType.Length) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + + if (length != null) + { + if(length < 0 || length > mediaType.Length) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + if (offset > mediaType.Length - length) + { + throw new ArgumentException(Resources.FormatArgument_InvalidOffsetLength(nameof(offset), nameof(length))); + } + } + + _parameterParser = default(MediaTypeParameterParser); + + var typeLength = GetTypeLength(mediaType, offset, out var type); + if (typeLength == 0) + { + Type = new StringSegment(); + SubType = new StringSegment(); + SubTypeWithoutSuffix = new StringSegment(); + SubTypeSuffix = new StringSegment(); + return; + } + else + { + Type = type; + } + + var subTypeLength = GetSubtypeLength(mediaType, offset + typeLength, out var subType); + if (subTypeLength == 0) + { + SubType = new StringSegment(); + SubTypeWithoutSuffix = new StringSegment(); + SubTypeSuffix = new StringSegment(); + return; + } + else + { + SubType = subType; + + if (TryGetSuffixLength(subType, out var subtypeSuffixLength)) + { + SubTypeWithoutSuffix = subType.Subsegment(0, subType.Length - subtypeSuffixLength - 1); + SubTypeSuffix = subType.Subsegment(subType.Length - subtypeSuffixLength, subtypeSuffixLength); + } + else + { + SubTypeWithoutSuffix = SubType; + SubTypeSuffix = new StringSegment(); + } + } + + _parameterParser = new MediaTypeParameterParser(mediaType, offset + typeLength + subTypeLength, length); + } + + // All GetXXXLength methods work in the same way. They expect to be on the right position for + // the token they are parsing, for example, the beginning of the media type or the delimiter + // from a previous token, like '/', ';' or '='. + // Each method consumes the delimiter token if any, the leading whitespace, then the given token + // itself, and finally the trailing whitespace. + private static int GetTypeLength(string input, int offset, out StringSegment type) + { + if (offset < 0 || offset >= input.Length) + { + type = default(StringSegment); + return 0; + } + + var current = offset + HttpTokenParsingRules.GetWhitespaceLength(input, offset); + + // Parse the type, i.e. in media type string "/; param1=value1; param2=value2" + var typeLength = HttpTokenParsingRules.GetTokenLength(input, current); + if (typeLength == 0) + { + type = default(StringSegment); + return 0; + } + + type = new StringSegment(input, current, typeLength); + + current += typeLength; + current += HttpTokenParsingRules.GetWhitespaceLength(input, current); + + return current - offset; + } + + private static int GetSubtypeLength(string input, int offset, out StringSegment subType) + { + var current = offset; + + // Parse the separator between type and subtype + if (current < 0 || current >= input.Length || input[current] != '/') + { + subType = default(StringSegment); + return 0; + } + + current++; // skip delimiter. + current += HttpTokenParsingRules.GetWhitespaceLength(input, current); + + var subtypeLength = HttpTokenParsingRules.GetTokenLength(input, current); + if (subtypeLength == 0) + { + subType = default(StringSegment); + return 0; + } + + subType = new StringSegment(input, current, subtypeLength); + + current += subtypeLength; + current += HttpTokenParsingRules.GetWhitespaceLength(input, current); + + return current - offset; + } + + private static bool TryGetSuffixLength(StringSegment subType, out int suffixLength) + { + // Find the last instance of '+', if there is one + var startPos = subType.Offset + subType.Length - 1; + for (var currentPos = startPos; currentPos >= subType.Offset; currentPos--) + { + if (subType.Buffer[currentPos] == '+') + { + suffixLength = startPos - currentPos; + return true; + } + } + + suffixLength = 0; + return false; + } + + /// + /// Gets the type of the . + /// + /// + /// For the media type "application/json", this property gives the value "application". + /// + public StringSegment Type { get; } + + /// + /// Gets whether this matches all types. + /// + public bool MatchesAllTypes => Type.Equals("*", StringComparison.OrdinalIgnoreCase); + + /// + /// Gets the subtype of the . + /// + /// + /// For the media type "application/vnd.example+json", this property gives the value + /// "vnd.example+json". + /// + public StringSegment SubType { get; } + + /// + /// Gets the subtype of the , excluding any structured syntax suffix. + /// + /// + /// For the media type "application/vnd.example+json", this property gives the value + /// "vnd.example". + /// + public StringSegment SubTypeWithoutSuffix { get; } + + /// + /// Gets the structured syntax suffix of the if it has one. + /// + /// + /// For the media type "application/vnd.example+json", this property gives the value + /// "json". + /// + public StringSegment SubTypeSuffix { get; } + + /// + /// Gets whether this matches all subtypes. + /// + /// + /// For the media type "application/*", this property is true. + /// + /// + /// For the media type "application/json", this property is false. + /// + public bool MatchesAllSubTypes => SubType.Equals("*", StringComparison.OrdinalIgnoreCase); + + /// + /// Gets whether this matches all subtypes, ignoring any structured syntax suffix. + /// + /// + /// For the media type "application/*+json", this property is true. + /// + /// + /// For the media type "application/vnd.example+json", this property is false. + /// + public bool MatchesAllSubTypesWithoutSuffix => SubTypeWithoutSuffix.Equals("*", StringComparison.OrdinalIgnoreCase); + + /// + /// Gets the of the if it has one. + /// + public Encoding Encoding => GetEncodingFromCharset(GetParameter("charset")); + + /// + /// Gets the charset parameter of the if it has one. + /// + public StringSegment Charset => GetParameter("charset"); + + /// + /// Determines whether the current contains a wildcard. + /// + /// + /// true if this contains a wildcard; otherwise false. + /// + public bool HasWildcard + { + get + { + return MatchesAllTypes || + MatchesAllSubTypesWithoutSuffix || + GetParameter("*").Equals("*", StringComparison.OrdinalIgnoreCase); + } + } + + /// + /// Determines whether the current is a subset of the + /// . + /// + /// The set . + /// + /// true if this is a subset of ; otherwise false. + /// + public bool IsSubsetOf(MediaType set) + { + return MatchesType(set) && + MatchesSubtype(set) && + ContainsAllParameters(set._parameterParser); + } + + /// + /// Gets the parameter of the media type. + /// + /// The name of the parameter to retrieve. + /// + /// The for the given if found; otherwise + /// null. + /// + public StringSegment GetParameter(string parameterName) + { + return GetParameter(new StringSegment(parameterName)); + } + + /// + /// Gets the parameter of the media type. + /// + /// The name of the parameter to retrieve. + /// + /// The for the given if found; otherwise + /// null. + /// + public StringSegment GetParameter(StringSegment parameterName) + { + var parametersParser = _parameterParser; + + while (parametersParser.ParseNextParameter(out var parameter)) + { + if (parameter.HasName(parameterName)) + { + return parameter.Value; + } + } + + return new StringSegment(); + } + + /// + /// Replaces the encoding of the given with the provided + /// . + /// + /// The media type whose encoding will be replaced. + /// The encoding that will replace the encoding in the . + /// + /// A media type with the replaced encoding. + public static string ReplaceEncoding(string mediaType, Encoding encoding) + { + return ReplaceEncoding(new StringSegment(mediaType), encoding); + } + + /// + /// Replaces the encoding of the given with the provided + /// . + /// + /// The media type whose encoding will be replaced. + /// The encoding that will replace the encoding in the . + /// + /// A media type with the replaced encoding. + public static string ReplaceEncoding(StringSegment mediaType, Encoding encoding) + { + var parsedMediaType = new MediaType(mediaType); + var charset = parsedMediaType.GetParameter("charset"); + + if (charset.HasValue && charset.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase)) + { + return mediaType.Value; + } + + if (!charset.HasValue) + { + return CreateMediaTypeWithEncoding(mediaType, encoding); + } + + var charsetOffset = charset.Offset - mediaType.Offset; + var restOffset = charsetOffset + charset.Length; + var restLength = mediaType.Length - restOffset; + var finalLength = charsetOffset + encoding.WebName.Length + restLength; + + var builder = new StringBuilder(mediaType.Buffer, mediaType.Offset, charsetOffset, finalLength); + builder.Append(encoding.WebName); + builder.Append(mediaType.Buffer, restOffset, restLength); + + return builder.ToString(); + } + + public static Encoding GetEncoding(string mediaType) + { + return GetEncoding(new StringSegment(mediaType)); + } + + public static Encoding GetEncoding(StringSegment mediaType) + { + var parsedMediaType = new MediaType(mediaType); + return parsedMediaType.Encoding; + } + + /// + /// Creates an containing the media type in + /// and its associated quality. + /// + /// The media type to parse. + /// The position at which the parsing starts. + /// The parsed media type with its associated quality. + public static MediaTypeSegmentWithQuality CreateMediaTypeSegmentWithQuality(string mediaType, int start) + { + var parsedMediaType = new MediaType(mediaType, start, length: null); + + // Short-circuit use of the MediaTypeParameterParser if constructor detected an invalid type or subtype. + // Parser would set ParsingFailed==true in this case. But, we handle invalid parameters as a separate case. + if (parsedMediaType.Type.Equals(default(StringSegment)) || + parsedMediaType.SubType.Equals(default(StringSegment))) + { + return default(MediaTypeSegmentWithQuality); + } + + var parser = parsedMediaType._parameterParser; + + var quality = 1.0d; + while (parser.ParseNextParameter(out var parameter)) + { + if (parameter.HasName(QualityParameter)) + { + // If media type contains two `q` values i.e. it's invalid in an uncommon way, pick last value. + quality = double.Parse( + parameter.Value.Value, NumberStyles.AllowDecimalPoint, + NumberFormatInfo.InvariantInfo); + } + } + + // We check if the parsed media type has a value at this stage when we have iterated + // over all the parameters and we know if the parsing was successful. + if (parser.ParsingFailed) + { + return default(MediaTypeSegmentWithQuality); + } + + return new MediaTypeSegmentWithQuality( + new StringSegment(mediaType, start, parser.CurrentOffset - start), + quality); + } + + private static Encoding GetEncodingFromCharset(StringSegment charset) + { + if (charset.Equals("utf-8", StringComparison.OrdinalIgnoreCase)) + { + // This is an optimization for utf-8 that prevents the Substring caused by + // charset.Value + return Encoding.UTF8; + } + + try + { + // charset.Value might be an invalid encoding name as in charset=invalid. + // For that reason, we catch the exception thrown by Encoding.GetEncoding + // and return null instead. + return charset.HasValue ? Encoding.GetEncoding(charset.Value) : null; + } + catch (Exception) + { + return null; + } + } + + private static string CreateMediaTypeWithEncoding(StringSegment mediaType, Encoding encoding) + { + return $"{mediaType.Value}; charset={encoding.WebName}"; + } + + private bool MatchesType(MediaType set) + { + return set.MatchesAllTypes || + set.Type.Equals(Type, StringComparison.OrdinalIgnoreCase); + } + + private bool MatchesSubtype(MediaType set) + { + if (set.MatchesAllSubTypes) + { + return true; + } + + if (set.SubTypeSuffix.HasValue) + { + if (SubTypeSuffix.HasValue) + { + // Both the set and the media type being checked have suffixes, so both parts must match. + return MatchesSubtypeWithoutSuffix(set) && MatchesSubtypeSuffix(set); + } + else + { + // The set has a suffix, but the media type being checked doesn't. We never consider this to match. + return false; + } + } + 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); + } + } + + private bool MatchesSubtypeWithoutSuffix(MediaType set) + { + return set.MatchesAllSubTypesWithoutSuffix || + set.SubTypeWithoutSuffix.Equals(SubTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase); + } + + private bool MatchesSubtypeSuffix(MediaType set) + { + // We don't have support for wildcards on suffixes alone (e.g., "application/entity+*") + // because there's no clear use case for it. + return set.SubTypeSuffix.Equals(SubTypeSuffix, StringComparison.OrdinalIgnoreCase); + } + + private bool ContainsAllParameters(MediaTypeParameterParser setParameters) + { + var parameterFound = true; + while (setParameters.ParseNextParameter(out var setParameter) && parameterFound) + { + if (setParameter.HasName("q")) + { + // "q" and later parameters are not involved in media type matching. Quoting the RFC: The first + // "q" parameter (if any) separates the media-range parameter(s) from the accept-params. + break; + } + + if (setParameter.HasName("*")) + { + // A parameter named "*" has no effect on media type matching, as it is only used as an indication + // that the entire media type string should be treated as a wildcard. + continue; + } + + // Copy the parser as we need to iterate multiple times over it. + // We can do this because it's a struct + var subSetParameters = _parameterParser; + parameterFound = false; + while (subSetParameters.ParseNextParameter(out var subSetParameter) && !parameterFound) + { + parameterFound = subSetParameter.Equals(setParameter); + } + } + + return parameterFound; + } + + private struct MediaTypeParameterParser + { + private readonly string _mediaTypeBuffer; + private readonly int? _length; + + public MediaTypeParameterParser(string mediaTypeBuffer, int offset, int? length) + { + _mediaTypeBuffer = mediaTypeBuffer; + _length = length; + CurrentOffset = offset; + ParsingFailed = false; + } + + public int CurrentOffset { get; private set; } + + public bool ParsingFailed { get; private set; } + + public bool ParseNextParameter(out MediaTypeParameter result) + { + if (_mediaTypeBuffer == null) + { + ParsingFailed = true; + result = default(MediaTypeParameter); + return false; + } + + var parameterLength = GetParameterLength(_mediaTypeBuffer, CurrentOffset, out result); + CurrentOffset += parameterLength; + + if (parameterLength == 0) + { + ParsingFailed = _length != null && CurrentOffset < _length; + return false; + } + + return true; + } + + private static int GetParameterLength(string input, int startIndex, out MediaTypeParameter parsedValue) + { + if (OffsetIsOutOfRange(startIndex, input.Length) || input[startIndex] != ';') + { + parsedValue = default(MediaTypeParameter); + return 0; + } + + var nameLength = GetNameLength(input, startIndex, out var name); + + var current = startIndex + nameLength; + + if (nameLength == 0 || OffsetIsOutOfRange(current, input.Length) || input[current] != '=') + { + if (current == input.Length && name.Equals("*", StringComparison.OrdinalIgnoreCase)) + { + // As a special case, we allow a trailing ";*" to indicate a wildcard + // string allowing any other parameters. It's the same as ";*=*". + var asterisk = new StringSegment("*"); + parsedValue = new MediaTypeParameter(asterisk, asterisk); + return current - startIndex; + } + else + { + parsedValue = default(MediaTypeParameter); + return 0; + } + } + + var valueLength = GetValueLength(input, current, out var value); + + parsedValue = new MediaTypeParameter(name, value); + current += valueLength; + + return current - startIndex; + } + + private static int GetNameLength(string input, int startIndex, out StringSegment name) + { + var current = startIndex; + + current++; // skip ';' + current += HttpTokenParsingRules.GetWhitespaceLength(input, current); + + var nameLength = HttpTokenParsingRules.GetTokenLength(input, current); + if (nameLength == 0) + { + name = default(StringSegment); + return 0; + } + + name = new StringSegment(input, current, nameLength); + + current += nameLength; + current += HttpTokenParsingRules.GetWhitespaceLength(input, current); + + return current - startIndex; + } + + private static int GetValueLength(string input, int startIndex, out StringSegment value) + { + var current = startIndex; + + current++; // skip '='. + current += HttpTokenParsingRules.GetWhitespaceLength(input, current); + + var valueLength = HttpTokenParsingRules.GetTokenLength(input, current); + + if (valueLength == 0) + { + // A value can either be a token or a quoted string. Check if it is a quoted string. + var result = HttpTokenParsingRules.GetQuotedStringLength(input, current, out valueLength); + if (result != HttpParseResult.Parsed) + { + // We have an invalid value. Reset the name and return. + value = default(StringSegment); + return 0; + } + + // Quotation marks are not part of a quoted parameter value. + value = new StringSegment(input, current + 1, valueLength - 2); + } + else + { + value = new StringSegment(input, current, valueLength); + } + + current += valueLength; + current += HttpTokenParsingRules.GetWhitespaceLength(input, current); + + return current - startIndex; + } + + private static bool OffsetIsOutOfRange(int offset, int length) + { + return offset < 0 || offset >= length; + } + } + + private struct MediaTypeParameter : IEquatable + { + public MediaTypeParameter(StringSegment name, StringSegment value) + { + Name = name; + Value = value; + } + + public StringSegment Name { get; } + + public StringSegment Value { get; } + + public bool HasName(string name) + { + return HasName(new StringSegment(name)); + } + + public bool HasName(StringSegment name) + { + return Name.Equals(name, StringComparison.OrdinalIgnoreCase); + } + + public bool Equals(MediaTypeParameter other) + { + return HasName(other.Name) && Value.Equals(other.Value, StringComparison.OrdinalIgnoreCase); + } + + public override string ToString() => $"{Name}={Value}"; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaTypeCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaTypeCollection.cs new file mode 100644 index 0000000000..b5b52f3ebb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaTypeCollection.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET 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.ObjectModel; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// A collection of media types. + /// + public class MediaTypeCollection : Collection + { + /// + /// Adds an object to the end of the . + /// + /// The media type to be added to the end of the . + public void Add(MediaTypeHeaderValue item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + Add(item.ToString()); + } + + /// + /// Inserts an element into the at the specified index. + /// + /// The zero-based index at which should be inserted. + /// The media type to insert. + public void Insert(int index, MediaTypeHeaderValue item) + { + if (index < 0 || index > Count) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + Insert(index, item.ToString()); + } + + /// + /// Removes the first occurrence of a specific media type from the . + /// + /// + /// true if is successfully removed; otherwise, false. + /// This method also returns false if was not found in the original + /// . + public bool Remove(MediaTypeHeaderValue item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + return Remove(item.ToString()); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs new file mode 100644 index 0000000000..fd0c37b972 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs @@ -0,0 +1,196 @@ +// Copyright (c) .NET 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.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Writes an object to the output stream. + /// + public abstract class OutputFormatter : IOutputFormatter, IApiResponseTypeMetadataProvider + { + /// + /// Gets the mutable collection of media type elements supported by + /// this . + /// + public MediaTypeCollection SupportedMediaTypes { get; } = new MediaTypeCollection(); + + /// + /// Returns a value indicating whether or not the given type can be written by this serializer. + /// + /// The object type. + /// true if the type can be written, otherwise false. + protected virtual bool CanWriteType(Type type) + { + return true; + } + + /// + public virtual IReadOnlyList GetSupportedContentTypes( + string contentType, + Type objectType) + { + if (SupportedMediaTypes.Count == 0) + { + var message = Resources.FormatFormatter_NoMediaTypes( + GetType().FullName, + nameof(SupportedMediaTypes)); + + throw new InvalidOperationException(message); + } + + if (!CanWriteType(objectType)) + { + return null; + } + + List mediaTypes = null; + + var parsedContentType = contentType != null ? new MediaType(contentType) : default(MediaType); + + foreach (var mediaType in SupportedMediaTypes) + { + var parsedMediaType = new MediaType(mediaType); + if (parsedMediaType.HasWildcard) + { + // For supported media types that are wildcard patterns, confirm that the requested + // media type satisfies the wildcard pattern (e.g., if "text/entity+json;v=2" requested + // and formatter supports "text/*+json"). + // Treat contentType like it came from a [Produces] attribute. + if (contentType != null && parsedContentType.IsSubsetOf(parsedMediaType)) + { + if (mediaTypes == null) + { + mediaTypes = new List(); + } + + mediaTypes.Add(contentType); + } + } + else + { + // Confirm this formatter supports a more specific media type than requested e.g. OK if "text/*" + // requested and formatter supports "text/plain". Treat contentType like it came from an Accept header. + if (contentType == null || parsedMediaType.IsSubsetOf(parsedContentType)) + { + if (mediaTypes == null) + { + mediaTypes = new List(); + } + + mediaTypes.Add(mediaType); + } + } + } + + return mediaTypes; + } + + /// + public virtual bool CanWriteResult(OutputFormatterCanWriteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (SupportedMediaTypes.Count == 0) + { + var message = Resources.FormatFormatter_NoMediaTypes( + GetType().FullName, + nameof(SupportedMediaTypes)); + + throw new InvalidOperationException(message); + } + + if (!CanWriteType(context.ObjectType)) + { + return false; + } + + if (!context.ContentType.HasValue) + { + // If the desired content type is set to null, then the current formatter can write anything + // it wants. + context.ContentType = new StringSegment(SupportedMediaTypes[0]); + return true; + } + else + { + var parsedContentType = new MediaType(context.ContentType); + for (var i = 0; i < SupportedMediaTypes.Count; i++) + { + var supportedMediaType = new MediaType(SupportedMediaTypes[i]); + if (supportedMediaType.HasWildcard) + { + // For supported media types that are wildcard patterns, confirm that the requested + // media type satisfies the wildcard pattern (e.g., if "text/entity+json;v=2" requested + // and formatter supports "text/*+json"). + // We only do this when comparing against server-defined content types (e.g., those + // from [Produces] or Response.ContentType), otherwise we'd potentially be reflecting + // back arbitrary Accept header values. + if (context.ContentTypeIsServerDefined + && parsedContentType.IsSubsetOf(supportedMediaType)) + { + return true; + } + } + else + { + // For supported media types that are not wildcard patterns, confirm that this formatter + // supports a more specific media type than requested e.g. OK if "text/*" requested and + // formatter supports "text/plain". + // contentType is typically what we got in an Accept header. + if (supportedMediaType.IsSubsetOf(parsedContentType)) + { + context.ContentType = new StringSegment(SupportedMediaTypes[i]); + return true; + } + } + } + } + + return false; + } + + /// + public virtual Task WriteAsync(OutputFormatterWriteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + WriteResponseHeaders(context); + return WriteResponseBodyAsync(context); + } + + /// + /// Sets the headers on object. + /// + /// The formatter context associated with the call. + public virtual void WriteResponseHeaders(OutputFormatterWriteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var response = context.HttpContext.Response; + response.ContentType = context.ContentType.Value; + } + + /// + /// Writes the response body. + /// + /// The formatter context associated with the call. + /// A task which can write the response body. + public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StreamOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StreamOutputFormatter.cs new file mode 100644 index 0000000000..a1a748a0f7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StreamOutputFormatter.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.IO; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Always copies the stream to the response, regardless of requested content type. + /// + public class StreamOutputFormatter : IOutputFormatter + { + /// + public bool CanWriteResult(OutputFormatterCanWriteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // Ignore the passed in content type, if the object is a Stream. + if (context.Object is Stream) + { + return true; + } + + return false; + } + + /// + public async Task WriteAsync(OutputFormatterWriteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + using (var valueAsStream = ((Stream)context.Object)) + { + var response = context.HttpContext.Response; + + if (context.ContentType != null) + { + response.ContentType = context.ContentType.ToString(); + } + + await valueAsStream.CopyToAsync(response.Body); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StringOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StringOutputFormatter.cs new file mode 100644 index 0000000000..52c387d137 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StringOutputFormatter.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.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// A for simple text content. + /// + public class StringOutputFormatter : TextOutputFormatter + { + public StringOutputFormatter() + { + SupportedEncodings.Add(Encoding.UTF8); + SupportedEncodings.Add(Encoding.Unicode); + SupportedMediaTypes.Add("text/plain"); + } + + public override bool CanWriteResult(OutputFormatterCanWriteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.ObjectType == typeof(string) || context.Object is string) + { + // Call into base to check if the current request's content type is a supported media type. + return base.CanWriteResult(context); + } + + return false; + } + + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding encoding) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + var valueAsString = (string)context.Object; + if (string.IsNullOrEmpty(valueAsString)) + { + return Task.CompletedTask; + } + + var response = context.HttpContext.Response; + return response.WriteAsync(valueAsString, encoding); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextInputFormatter.cs new file mode 100644 index 0000000000..cf104bacbc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextInputFormatter.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.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Reads an object from a request body with a text format. + /// + public abstract class TextInputFormatter : InputFormatter + { + /// + /// Returns UTF8 Encoding without BOM and throws on invalid bytes. + /// + protected static readonly Encoding UTF8EncodingWithoutBOM + = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + + /// + /// Returns UTF16 Encoding which uses littleEndian byte order with BOM and throws on invalid bytes. + /// + protected static readonly Encoding UTF16EncodingLittleEndian + = new UnicodeEncoding(bigEndian: false, byteOrderMark: true, throwOnInvalidBytes: true); + + /// + /// Gets the mutable collection of character encodings supported by + /// this . The encodings are + /// used when reading the data. + /// + public IList SupportedEncodings { get; } = new List(); + + /// + public override Task ReadRequestBodyAsync(InputFormatterContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var selectedEncoding = SelectCharacterEncoding(context); + if (selectedEncoding == null) + { + var message = Resources.FormatUnsupportedContentType( + context.HttpContext.Request.ContentType); + + var exception = new UnsupportedContentTypeException(message); + context.ModelState.AddModelError(context.ModelName, exception, context.Metadata); + + return InputFormatterResult.FailureAsync(); + } + + return ReadRequestBodyAsync(context, selectedEncoding); + } + + /// + /// Reads an object from the request body. + /// + /// The . + /// The used to read the request body. + /// A that on completion deserializes the request body. + public abstract Task ReadRequestBodyAsync( + InputFormatterContext context, + Encoding encoding); + + /// + /// Returns an based on 's + /// character set. + /// + /// The . + /// + /// An based on 's + /// character set. null if no supported encoding was found. + /// + protected Encoding SelectCharacterEncoding(InputFormatterContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (SupportedEncodings.Count == 0) + { + var message = Resources.FormatTextInputFormatter_SupportedEncodingsMustNotBeEmpty( + nameof(SupportedEncodings)); + throw new InvalidOperationException(message); + } + + var requestContentType = context.HttpContext.Request.ContentType; + var requestMediaType = requestContentType == null ? default(MediaType) : new MediaType(requestContentType); + if (requestMediaType.Charset.HasValue) + { + // Create Encoding based on requestMediaType.Charset to support charset aliases and custom Encoding + // providers. Charset -> Encoding -> encoding.WebName chain canonicalizes the charset name. + var requestEncoding = requestMediaType.Encoding; + if (requestEncoding != null) + { + for (int i = 0; i < SupportedEncodings.Count; i++) + { + if (string.Equals( + requestEncoding.WebName, + SupportedEncodings[i].WebName, + StringComparison.OrdinalIgnoreCase)) + { + return SupportedEncodings[i]; + } + } + } + + // The client specified an encoding in the content type header of the request + // but we don't understand it. In this situation we don't try to pick any other encoding + // from the list of supported encodings and read the body with that encoding. + // Instead, we return null and that will translate later on into a 415 Unsupported Media Type + // response. + return null; + } + + // We want to do our best effort to read the body of the request even in the + // cases where the client doesn't send a content type header or sends a content + // type header without encoding. For that reason we pick the first encoding of the + // list of supported encodings and try to use that to read the body. This encoding + // is UTF-8 by default in our formatters, which generally is a safe choice for the + // encoding. + return SupportedEncodings[0]; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextOutputFormatter.cs new file mode 100644 index 0000000000..c30901da00 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextOutputFormatter.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 System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Writes an object in a given text format to the output stream. + /// + public abstract class TextOutputFormatter : OutputFormatter + { + private IDictionary _outputMediaTypeCache; + + /// + /// Initializes a new instance of the class. + /// + protected TextOutputFormatter() + { + SupportedEncodings = new List(); + } + + /// + /// Gets the mutable collection of character encodings supported by + /// this . The encodings are + /// used when writing the data. + /// + public IList SupportedEncodings { get; } + + private IDictionary OutputMediaTypeCache + { + get + { + if (_outputMediaTypeCache == null) + { + var cache = new Dictionary(); + foreach (var mediaType in SupportedMediaTypes) + { + cache.Add(mediaType, MediaType.ReplaceEncoding(mediaType, Encoding.UTF8)); + } + + // Safe race condition, worst case scenario we initialize the field multiple times with dictionaries containing + // the same values. + _outputMediaTypeCache = cache; + } + + return _outputMediaTypeCache; + } + } + + /// + /// Determines the best amongst the supported encodings + /// for reading or writing an HTTP entity body based on the provided content type. + /// + /// The formatter context associated with the call. + /// + /// The to use when reading the request or writing the response. + public virtual Encoding SelectCharacterEncoding(OutputFormatterWriteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (SupportedEncodings.Count == 0) + { + var message = Resources.FormatTextOutputFormatter_SupportedEncodingsMustNotBeEmpty( + nameof(SupportedEncodings)); + throw new InvalidOperationException(message); + } + + var acceptCharsetHeaderValues = GetAcceptCharsetHeaderValues(context); + var encoding = MatchAcceptCharacterEncoding(acceptCharsetHeaderValues); + if (encoding != null) + { + return encoding; + } + + if (context.ContentType.HasValue) + { + var parsedContentType = new MediaType(context.ContentType); + var contentTypeCharset = parsedContentType.Charset; + if (contentTypeCharset.HasValue) + { + for (var i = 0; i < SupportedEncodings.Count; i++) + { + var supportedEncoding = SupportedEncodings[i]; + if (contentTypeCharset.Equals(supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase)) + { + // This is supported. + return SupportedEncodings[i]; + } + } + } + } + + return SupportedEncodings[0]; + } + + /// + public override Task WriteAsync(OutputFormatterWriteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var selectedMediaType = context.ContentType; + if (!selectedMediaType.HasValue) + { + // If content type is not set then set it based on supported media types. + if (SupportedEncodings.Count > 0) + { + selectedMediaType = new StringSegment(SupportedMediaTypes[0]); + } + else + { + throw new InvalidOperationException(Resources.FormatOutputFormatterNoMediaType(GetType().FullName)); + } + } + + var selectedEncoding = SelectCharacterEncoding(context); + if (selectedEncoding != null) + { + // Override the content type value even if one already existed. + var mediaTypeWithCharset = GetMediaTypeWithCharset(selectedMediaType.Value, selectedEncoding); + selectedMediaType = new StringSegment(mediaTypeWithCharset); + } + else + { + var response = context.HttpContext.Response; + response.StatusCode = StatusCodes.Status406NotAcceptable; + return Task.CompletedTask; + } + + context.ContentType = selectedMediaType; + + WriteResponseHeaders(context); + return WriteResponseBodyAsync(context, selectedEncoding); + } + + /// + public sealed override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) + { + var message = Resources.FormatTextOutputFormatter_WriteResponseBodyAsyncNotSupported( + $"{nameof(WriteResponseBodyAsync)}({nameof(OutputFormatterWriteContext)})", + nameof(TextOutputFormatter), + $"{nameof(WriteResponseBodyAsync)}({nameof(OutputFormatterWriteContext)},{nameof(Encoding)})"); + + throw new InvalidOperationException(message); + } + + /// + /// Writes the response body. + /// + /// The formatter context associated with the call. + /// The that should be used to write the response. + /// A task which can write the response body. + public abstract Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding); + + internal static IList GetAcceptCharsetHeaderValues(OutputFormatterWriteContext context) + { + var request = context.HttpContext.Request; + if (StringWithQualityHeaderValue.TryParseList(request.Headers[HeaderNames.AcceptCharset], out IList result)) + { + return result; + } + + return Array.Empty(); + } + + private string GetMediaTypeWithCharset(string mediaType, Encoding encoding) + { + if (string.Equals(encoding.WebName, Encoding.UTF8.WebName, StringComparison.OrdinalIgnoreCase) && + OutputMediaTypeCache.ContainsKey(mediaType)) + { + return OutputMediaTypeCache[mediaType]; + } + + return MediaType.ReplaceEncoding(mediaType, encoding); + } + + private Encoding MatchAcceptCharacterEncoding(IList acceptCharsetHeaders) + { + if (acceptCharsetHeaders != null && acceptCharsetHeaders.Count > 0) + { + var acceptValues = Sort(acceptCharsetHeaders); + for (var i = 0; i < acceptValues.Count; i++) + { + var charset = acceptValues[i].Value; + if (!StringSegment.IsNullOrEmpty(charset)) + { + for (var j = 0; j < SupportedEncodings.Count; j++) + { + var encoding = SupportedEncodings[j]; + if (charset.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase) || + charset.Equals("*", StringComparison.Ordinal)) + { + return encoding; + } + } + } + } + } + + return null; + } + + // There's no allocation-free way to sort an IList and we may have to filter anyway, + // so we're going to have to live with the copy + insertion sort. + private IList Sort(IList values) + { + var sortNeeded = false; + + for (var i = 0; i < values.Count; i++) + { + var value = values[i]; + if (value.Quality == HeaderQuality.NoMatch) + { + // Exclude this one + } + else if (value.Quality != null) + { + sortNeeded = true; + } + } + + if (!sortNeeded) + { + return values; + } + + var sorted = new List(); + for (var i = 0; i < values.Count; i++) + { + var value = values[i]; + if (value.Quality == HeaderQuality.NoMatch) + { + // Exclude this one + } + else + { + // Doing an insertion sort. + var position = sorted.BinarySearch(value, StringWithQualityHeaderValueComparer.QualityComparer); + if (position >= 0) + { + sorted.Insert(position + 1, value); + } + else + { + sorted.Insert(~position, value); + } + } + } + + // We want a descending sort, but BinarySearch does ascending + sorted.Reverse(); + return sorted; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromBodyAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromBodyAttribute.cs new file mode 100644 index 0000000000..157d1cdd3d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromBodyAttribute.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.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies that a parameter or property should be bound using the request body. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class FromBodyAttribute : Attribute, IBindingSourceMetadata + { + /// + public BindingSource BindingSource => BindingSource.Body; + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromFormAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromFormAttribute.cs new file mode 100644 index 0000000000..a42774f17f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromFormAttribute.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 System; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies that a parameter or property should be bound using form-data in the request body. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class FromFormAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider + { + /// + public BindingSource BindingSource => BindingSource.Form; + + /// + public string Name { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromHeaderAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromHeaderAttribute.cs new file mode 100644 index 0000000000..46809454f1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromHeaderAttribute.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 System; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies that a parameter or property should be bound using the request headers. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class FromHeaderAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider + { + /// + public BindingSource BindingSource => BindingSource.Header; + + /// + public string Name { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromQueryAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromQueryAttribute.cs new file mode 100644 index 0000000000..df82f67bc3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromQueryAttribute.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 System; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies that a parameter or property should be bound using the request query string. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class FromQueryAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider + { + /// + public BindingSource BindingSource => BindingSource.Query; + + /// + public string Name { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromRouteAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromRouteAttribute.cs new file mode 100644 index 0000000000..94952e502f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromRouteAttribute.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 System; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies that a parameter or property should be bound using route-data from the current request. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class FromRouteAttribute : Attribute, IBindingSourceMetadata, IModelNameProvider + { + /// + public BindingSource BindingSource => BindingSource.Path; + + /// + public string Name { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromServicesAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromServicesAttribute.cs new file mode 100644 index 0000000000..271ae181f2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromServicesAttribute.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; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies that an action parameter should be bound using the request services. + /// + /// + /// In this example an implementation of IProductModelRequestService is registered as a service. + /// Then in the GetProduct action, the parameter is bound to an instance of IProductModelRequestService + /// which is resolved from the request services. + /// + /// + /// [HttpGet] + /// public ProductModel GetProduct([FromServices] IProductModelRequestService productModelReqest) + /// { + /// return productModelReqest.Value; + /// } + /// + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public class FromServicesAttribute : Attribute, IBindingSourceMetadata + { + /// + public BindingSource BindingSource => BindingSource.Services; + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpDeleteAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpDeleteAttribute.cs new file mode 100644 index 0000000000..b0bd31e3cc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpDeleteAttribute.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Identifies an action that only supports the HTTP DELETE method. + /// + public class HttpDeleteAttribute : HttpMethodAttribute + { + private static readonly IEnumerable _supportedMethods = new [] { "DELETE" }; + + /// + /// Creates a new . + /// + public HttpDeleteAttribute() + : base(_supportedMethods) + { + } + + /// + /// Creates a new with the given route template. + /// + /// The route template. May not be null. + public HttpDeleteAttribute(string template) + : base(_supportedMethods, template) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpGetAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpGetAttribute.cs new file mode 100644 index 0000000000..1b8a9501ef --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpGetAttribute.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Identifies an action that only supports the HTTP GET method. + /// + public class HttpGetAttribute : HttpMethodAttribute + { + private static readonly IEnumerable _supportedMethods = new [] { "GET" }; + + /// + /// Creates a new . + /// + public HttpGetAttribute() + : base(_supportedMethods) + { + } + + /// + /// Creates a new with the given route template. + /// + /// The route template. May not be null. + public HttpGetAttribute(string template) + : base(_supportedMethods, template) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpHeadAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpHeadAttribute.cs new file mode 100644 index 0000000000..43e6c903e6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpHeadAttribute.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Identifies an action that only supports the HTTP HEAD method. + /// + public class HttpHeadAttribute : HttpMethodAttribute + { + private static readonly IEnumerable _supportedMethods = new [] { "HEAD" }; + + /// + /// Creates a new . + /// + public HttpHeadAttribute() + : base(_supportedMethods) + { + } + + /// + /// Creates a new with the given route template. + /// + /// The route template. May not be null. + public HttpHeadAttribute(string template) + : base(_supportedMethods, template) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpOptionsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpOptionsAttribute.cs new file mode 100644 index 0000000000..da08f04497 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpOptionsAttribute.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Identifies an action that only supports the HTTP OPTIONS method. + /// + public class HttpOptionsAttribute : HttpMethodAttribute + { + private static readonly IEnumerable _supportedMethods = new [] { "OPTIONS" }; + + /// + /// Creates a new . + /// + public HttpOptionsAttribute() + : base(_supportedMethods) + { + } + + /// + /// Creates a new with the given route template. + /// + /// The route template. May not be null. + public HttpOptionsAttribute(string template) + : base(_supportedMethods, template) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPatchAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPatchAttribute.cs new file mode 100644 index 0000000000..4d7e01d829 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPatchAttribute.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Identifies an action that only supports the HTTP PATCH method. + /// + public class HttpPatchAttribute : HttpMethodAttribute + { + private static readonly IEnumerable _supportedMethods = new [] { "PATCH" }; + + /// + /// Creates a new . + /// + public HttpPatchAttribute() + : base(_supportedMethods) + { + } + + /// + /// Creates a new with the given route template. + /// + /// The route template. May not be null. + public HttpPatchAttribute(string template) + : base(_supportedMethods, template) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPostAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPostAttribute.cs new file mode 100644 index 0000000000..cf9e12d5cc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPostAttribute.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Identifies an action that only supports the HTTP POST method. + /// + public class HttpPostAttribute : HttpMethodAttribute + { + private static readonly IEnumerable _supportedMethods = new [] { "POST" }; + + /// + /// Creates a new . + /// + public HttpPostAttribute() + : base(_supportedMethods) + { + } + + /// + /// Creates a new with the given route template. + /// + /// The route template. May not be null. + public HttpPostAttribute(string template) + : base(_supportedMethods, template) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPutAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPutAttribute.cs new file mode 100644 index 0000000000..09e28237d8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPutAttribute.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Identifies an action that only supports the HTTP PUT method. + /// + public class HttpPutAttribute : HttpMethodAttribute + { + private static readonly IEnumerable _supportedMethods = new [] { "PUT" }; + + /// + /// Creates a new . + /// + public HttpPutAttribute() + : base(_supportedMethods) + { + } + + /// + /// Creates a new with the given route template. + /// + /// The route template. May not be null. + public HttpPutAttribute(string template) + : base(_supportedMethods, template) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IDesignTimeMvcBuilderConfiguration.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IDesignTimeMvcBuilderConfiguration.cs new file mode 100644 index 0000000000..312642b052 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IDesignTimeMvcBuilderConfiguration.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.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Configures the . Implement this interface to enable design-time configuration + /// (for instance during pre-compilation of views) of . + /// + public interface IDesignTimeMvcBuilderConfiguration + { + /// + /// Configures the . + /// + /// The . + void ConfigureMvc(IMvcBuilder builder); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IRequestFormLimitsPolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IRequestFormLimitsPolicy.cs new file mode 100644 index 0000000000..c55608d59d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IRequestFormLimitsPolicy.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.Filters; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A marker interface for filters which define a policy for limits on a request's body read as a form. + /// + public interface IRequestFormLimitsPolicy : IFilterMetadata + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IRequestSizePolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IRequestSizePolicy.cs new file mode 100644 index 0000000000..6005da9b9b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IRequestSizePolicy.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.Filters; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A marker interface for filters which define a policy for maximum size for the request body. + /// + public interface IRequestSizePolicy : IFilterMetadata + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionContextAccessor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionContextAccessor.cs new file mode 100644 index 0000000000..c12acdc36c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionContextAccessor.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.Threading; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class ActionContextAccessor : IActionContextAccessor + { + private static readonly AsyncLocal _storage = new AsyncLocal(); + + public ActionContext ActionContext + { + get { return _storage.Value; } + set { _storage.Value = value; } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollection.cs new file mode 100644 index 0000000000..dc00290a5d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollection.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// A cached collection of . + /// + public class ActionDescriptorCollection + { + /// + /// Initializes a new instance of the . + /// + /// The result of action discovery + /// The unique version of discovered actions. + public ActionDescriptorCollection(IReadOnlyList items, int version) + { + if (items == null) + { + throw new ArgumentNullException(nameof(items)); + } + + Items = items; + Version = version; + } + + /// + /// Returns the cached . + /// + public IReadOnlyList Items { get; } + + /// + /// Returns the unique version of the currently cached items. + /// + public int Version { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/CompatibilitySwitch.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/CompatibilitySwitch.cs new file mode 100644 index 0000000000..0c070ecbe4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/CompatibilitySwitch.cs @@ -0,0 +1,129 @@ +// Copyright (c) .NET 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 +{ + // Guide to making breaking behavior changes in MVC: + // + // Hello, if you're reading this, you're probably trying to make a change in behavior in MVC in a minor + // version. Every change in behavior is a breaking change to someone, even if a feature was buggy or + // broken in some scenarios. + // + // To help make things easier for current users, we don't automatically opt users into breaking changes when + // they upgrade applications to a new minor version of ASP.NET Core. It's a separate choice to opt in to new + // behaviors in a minor release. + // + // To make things better for future users, we also want to provide an easy way for applications to get + // access to the new behaviors. We make changes when they are improvements, and if we're changing something + // we've already shipped, it must add value for all of our users (eventually). To this end, new applications + // created using the template are always opted in to the 'current' version. + // + // This means that all changes in behavior should be opt-in. + // + // ----- + // + // Moving on from general philosophy, here's how to implement a behavior change and corresponding + // compatibility switch. + // + // Add a new property on options that uses a CompatibilitySwitch as a backing field. Make sure the + // new switch is exposed by implementing IEnumerable on the options class. Pass the + // property name to the CompatibilitySwitch constructor using nameof. + // + // Choose a boolean value or a new enum type as the 'value' of the property. + // + // If the new property has a boolean value, it should be named something like `SuppressFoo` + // (if the new value deactivates some behavior) or like `AllowFoo` (if the new value enables some behavior). + // Choose a name so that the old behavior equates to 'false'. + // + // If it's an enum, make sure you initialize the compatibility switch using the + // CompatibilitySwitch(string, value) constructor to make it obvious the correct value is passed in. It's + // a good idea to equate the original behavior with the default enum value as well. + // + // Then create (or modify) a subclass of ConfigureCompatibilityOptions appropriate for your options type. + // Override the DefaultValues property and provide appropriate values based on the value of the Version + // property. If you just added this class, register it as an IPostConfigureOptions in DI. + // + /// + /// Infrastructure supporting the implementation of . This is an + /// implementation of suitable for use with the + /// pattern. This is framework infrastructure and should not be used by application code. + /// + /// The type of value associated with the compatibility switch. + public class CompatibilitySwitch : ICompatibilitySwitch where TValue : struct + { + private TValue _value; + + /// + /// Creates a new compatibility switch with the provided name. + /// + /// + /// The compatibility switch name. The name must match a property name on an options type. + /// + public CompatibilitySwitch(string name) + : this(name, default) + { + } + + /// + /// Creates a new compatibility switch with the provided name and initial value. + /// + /// + /// The compatibility switch name. The name must match a property name on an options type. + /// + /// + /// The initial value to assign to the switch. + /// + public CompatibilitySwitch(string name, TValue initialValue) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + Name = name; + _value = initialValue; + } + + /// + /// Gets a value indicating whether the property has been set. + /// + /// + /// This is used by the compatibility infrastructure to determine whether the application developer + /// has set explicitly set the value associated with this switch. + /// + public bool IsValueSet { get; private set; } + + /// + /// Gets the name of the compatibility switch. + /// + public string Name { get; } + + /// + /// Gets or set the value associated with the compatibility switch. + /// + /// + /// Setting the switch value using will set to true. + /// As a consequence, the compatibility infrastructure will consider this switch explicitly configured by + /// the application developer, and will not apply a default value based on the compatibility version. + /// + public TValue Value + { + get => _value; + set + { + IsValueSet = true; + _value = value; + } + } + + // Called by the compatibility infrastructure to set a default value when IsValueSet is false. + object ICompatibilitySwitch.Value + { + get => Value; + set => Value = (TValue)value; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ConfigureCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ConfigureCompatibilityOptions.cs new file mode 100644 index 0000000000..c46a3529d0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ConfigureCompatibilityOptions.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 Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// 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. + /// + /// + public abstract class ConfigureCompatibilityOptions : IPostConfigureOptions + where TOptions : class, IEnumerable + { + private readonly ILogger _logger; + + /// + /// Creates a new . + /// + /// The . + /// The . + protected ConfigureCompatibilityOptions( + ILoggerFactory loggerFactory, + IOptions compatibilityOptions) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + Version = compatibilityOptions.Value.CompatibilityVersion; + _logger = loggerFactory.CreateLogger(); + } + + /// + /// Gets the default values of compatibility switches associated with the applications configured + /// . + /// + protected abstract IReadOnlyDictionary DefaultValues { get; } + + /// + /// Gets the configured for the application. + /// + protected CompatibilityVersion Version { get; } + + /// + public virtual void PostConfigure(string name, TOptions options) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + // Evaluate DefaultValues onces so subclasses don't have to cache. + var defaultValues = DefaultValues; + + foreach (var @switch in options) + { + ConfigureSwitch(@switch, defaultValues); + } + } + + private void ConfigureSwitch(ICompatibilitySwitch @switch, IReadOnlyDictionary defaultValues) + { + if (@switch.IsValueSet) + { + _logger.LogDebug( + "Compatibility switch {SwitchName} in type {OptionsType} is using explicitly configured value {Value}", + @switch.Name, + typeof(TOptions).Name, + @switch.Value); + return; + } + + if (!defaultValues.TryGetValue(@switch.Name, out var value)) + { + _logger.LogDebug( + "Compatibility switch {SwitchName} in type {OptionsType} is using default value {Value}", + @switch.Name, + typeof(TOptions).Name, + @switch.Value, + Version); + return; + } + + @switch.Value = value; + _logger.LogDebug( + "Compatibility switch {SwitchName} in type {OptionsType} is using compatibility value {Value} for version {Version}", + @switch.Name, + typeof(TOptions).Name, + @switch.Value, + Version); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ContentResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ContentResultExecutor.cs new file mode 100644 index 0000000000..7a1f333b3a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ContentResultExecutor.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET 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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class ContentResultExecutor : IActionResultExecutor + { + private const string DefaultContentType = "text/plain; charset=utf-8"; + private readonly ILogger _logger; + private readonly IHttpResponseStreamWriterFactory _httpResponseStreamWriterFactory; + + public ContentResultExecutor(ILogger logger, IHttpResponseStreamWriterFactory httpResponseStreamWriterFactory) + { + _logger = logger; + _httpResponseStreamWriterFactory = httpResponseStreamWriterFactory; + } + + /// + public virtual async Task ExecuteAsync(ActionContext context, ContentResult result) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var response = context.HttpContext.Response; + + string resolvedContentType; + Encoding resolvedContentTypeEncoding; + ResponseContentTypeHelper.ResolveContentTypeAndEncoding( + result.ContentType, + response.ContentType, + DefaultContentType, + out resolvedContentType, + out resolvedContentTypeEncoding); + + response.ContentType = resolvedContentType; + + if (result.StatusCode != null) + { + response.StatusCode = result.StatusCode.Value; + } + + _logger.ContentResultExecuting(resolvedContentType); + + if (result.Content != null) + { + response.ContentLength = resolvedContentTypeEncoding.GetByteCount(result.Content); + + using (var textWriter = _httpResponseStreamWriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding)) + { + await textWriter.WriteAsync(result.Content); + + // Flushing the HttpResponseStreamWriter does not flush the underlying stream. This just flushes + // the buffered text in the writer. + // We do this rather than letting dispose handle it because dispose would call Write and we want + // to call WriteAsync. + await textWriter.FlushAsync(); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultOutputFormatterSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultOutputFormatterSelector.cs new file mode 100644 index 0000000000..61a62ec63f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultOutputFormatterSelector.cs @@ -0,0 +1,337 @@ +// Copyright (c) .NET 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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Formatters.Internal; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class DefaultOutputFormatterSelector : OutputFormatterSelector + { + private static readonly Comparison _sortFunction = (left, right) => + { + return left.Quality > right.Quality ? -1 : (left.Quality == right.Quality ? 0 : 1); + }; + + private readonly ILogger _logger; + private readonly IList _formatters; + private readonly bool _respectBrowserAcceptHeader; + private readonly bool _returnHttpNotAcceptable; + + public DefaultOutputFormatterSelector(IOptions options, ILoggerFactory loggerFactory) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _logger = loggerFactory.CreateLogger(); + + _formatters = new ReadOnlyCollection(options.Value.OutputFormatters); + _respectBrowserAcceptHeader = options.Value.RespectBrowserAcceptHeader; + _returnHttpNotAcceptable = options.Value.ReturnHttpNotAcceptable; + } + + public override IOutputFormatter SelectFormatter(OutputFormatterCanWriteContext context, IList formatters, MediaTypeCollection contentTypes) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (formatters == null) + { + throw new ArgumentNullException(nameof(formatters)); + } + + if (contentTypes == null) + { + throw new ArgumentNullException(nameof(contentTypes)); + } + + ValidateContentTypes(contentTypes); + + if (formatters.Count == 0) + { + formatters = _formatters; + if (formatters.Count == 0) + { + throw new InvalidOperationException(Resources.FormatOutputFormattersAreRequired( + typeof(MvcOptions).FullName, + nameof(MvcOptions.OutputFormatters), + typeof(IOutputFormatter).FullName)); + } + } + + _logger.RegisteredOutputFormatters(formatters); + + var request = context.HttpContext.Request; + var acceptableMediaTypes = GetAcceptableMediaTypes(request); + var selectFormatterWithoutRegardingAcceptHeader = false; + + IOutputFormatter selectedFormatter = null; + if (acceptableMediaTypes.Count == 0) + { + // There is either no Accept header value, or it contained */* and we + // are not currently respecting the 'browser accept header'. + _logger.NoAcceptForNegotiation(); + + selectFormatterWithoutRegardingAcceptHeader = true; + } + else + { + if (contentTypes.Count == 0) + { + _logger.SelectingOutputFormatterUsingAcceptHeader(acceptableMediaTypes); + + // Use whatever formatter can meet the client's request + selectedFormatter = SelectFormatterUsingSortedAcceptHeaders( + context, + formatters, + acceptableMediaTypes); + } + else + { + _logger.SelectingOutputFormatterUsingAcceptHeaderAndExplicitContentTypes(acceptableMediaTypes, contentTypes); + + // Verify that a content type from the context is compatible with the client's request + selectedFormatter = SelectFormatterUsingSortedAcceptHeadersAndContentTypes( + context, + formatters, + acceptableMediaTypes, + contentTypes); + } + + if (selectedFormatter == null) + { + _logger.NoFormatterFromNegotiation(acceptableMediaTypes); + + if (!_returnHttpNotAcceptable) + { + selectFormatterWithoutRegardingAcceptHeader = true; + } + } + } + + if (selectFormatterWithoutRegardingAcceptHeader) + { + if (contentTypes.Count == 0) + { + _logger.SelectingOutputFormatterWithoutUsingContentTypes(); + + selectedFormatter = SelectFormatterNotUsingContentType( + context, + formatters); + } + else + { + _logger.SelectingOutputFormatterUsingContentTypes(contentTypes); + + selectedFormatter = SelectFormatterUsingAnyAcceptableContentType( + context, + formatters, + contentTypes); + } + } + + if (selectedFormatter == null) + { + // No formatter supports this. + _logger.NoFormatter(context); + return null; + } + + _logger.FormatterSelected(selectedFormatter, context); + return selectedFormatter; + } + + private List GetAcceptableMediaTypes(HttpRequest request) + { + var result = new List(); + AcceptHeaderParser.ParseAcceptHeader(request.Headers[HeaderNames.Accept], result); + for (var i = 0; i < result.Count; i++) + { + var mediaType = new MediaType(result[i].MediaType); + if (!_respectBrowserAcceptHeader && mediaType.MatchesAllSubTypes && mediaType.MatchesAllTypes) + { + result.Clear(); + return result; + } + } + + result.Sort(_sortFunction); + + return result; + } + + private IOutputFormatter SelectFormatterNotUsingContentType( + OutputFormatterCanWriteContext formatterContext, + IList formatters) + { + if (formatterContext == null) + { + throw new ArgumentNullException(nameof(formatterContext)); + } + + if (formatters == null) + { + throw new ArgumentNullException(nameof(formatters)); + } + + _logger.SelectFirstCanWriteFormatter(); + + foreach (var formatter in formatters) + { + formatterContext.ContentType = new StringSegment(); + formatterContext.ContentTypeIsServerDefined = false; + + if (formatter.CanWriteResult(formatterContext)) + { + return formatter; + } + } + + return null; + } + + private IOutputFormatter SelectFormatterUsingSortedAcceptHeaders( + OutputFormatterCanWriteContext formatterContext, + IList formatters, + IList sortedAcceptHeaders) + { + if (formatterContext == null) + { + throw new ArgumentNullException(nameof(formatterContext)); + } + + if (formatters == null) + { + throw new ArgumentNullException(nameof(formatters)); + } + + if (sortedAcceptHeaders == null) + { + throw new ArgumentNullException(nameof(sortedAcceptHeaders)); + } + + for (var i = 0; i < sortedAcceptHeaders.Count; i++) + { + var mediaType = sortedAcceptHeaders[i]; + + formatterContext.ContentType = mediaType.MediaType; + formatterContext.ContentTypeIsServerDefined = false; + + for (var j = 0; j < formatters.Count; j++) + { + var formatter = formatters[j]; + if (formatter.CanWriteResult(formatterContext)) + { + return formatter; + } + } + } + + return null; + } + + private IOutputFormatter SelectFormatterUsingAnyAcceptableContentType( + OutputFormatterCanWriteContext formatterContext, + IList formatters, + MediaTypeCollection acceptableContentTypes) + { + if (formatterContext == null) + { + throw new ArgumentNullException(nameof(formatterContext)); + } + + if (formatters == null) + { + throw new ArgumentNullException(nameof(formatters)); + } + + if (acceptableContentTypes == null) + { + throw new ArgumentNullException(nameof(acceptableContentTypes)); + } + + foreach (var formatter in formatters) + { + foreach (var contentType in acceptableContentTypes) + { + formatterContext.ContentType = new StringSegment(contentType); + formatterContext.ContentTypeIsServerDefined = true; + + if (formatter.CanWriteResult(formatterContext)) + { + return formatter; + } + } + } + + return null; + } + + private IOutputFormatter SelectFormatterUsingSortedAcceptHeadersAndContentTypes( + OutputFormatterCanWriteContext formatterContext, + IList formatters, + IList sortedAcceptableContentTypes, + MediaTypeCollection possibleOutputContentTypes) + { + for (var i = 0; i < sortedAcceptableContentTypes.Count; i++) + { + var acceptableContentType = new MediaType(sortedAcceptableContentTypes[i].MediaType); + for (var j = 0; j < possibleOutputContentTypes.Count; j++) + { + var candidateContentType = new MediaType(possibleOutputContentTypes[j]); + if (candidateContentType.IsSubsetOf(acceptableContentType)) + { + for (var k = 0; k < formatters.Count; k++) + { + var formatter = formatters[k]; + formatterContext.ContentType = new StringSegment(possibleOutputContentTypes[j]); + formatterContext.ContentTypeIsServerDefined = true; + if (formatter.CanWriteResult(formatterContext)) + { + return formatter; + } + } + } + } + } + + return null; + } + + private void ValidateContentTypes(MediaTypeCollection contentTypes) + { + for (var i = 0; i < contentTypes.Count; i++) + { + var contentType = contentTypes[i]; + + var parsedContentType = new MediaType(contentType); + if (parsedContentType.HasWildcard) + { + var message = Resources.FormatObjectResult_MatchAllContentType( + contentType, + nameof(ObjectResult.ContentTypes)); + throw new InvalidOperationException(message); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileContentResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileContentResultExecutor.cs new file mode 100644 index 0000000000..6d194c52f8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileContentResultExecutor.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 System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class FileContentResultExecutor : FileResultExecutorBase, IActionResultExecutor + { + public FileContentResultExecutor(ILoggerFactory loggerFactory) + : base(CreateLogger(loggerFactory)) + { + } + + /// + public virtual Task ExecuteAsync(ActionContext context, FileContentResult result) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + Logger.ExecutingFileResult(result); + + var (range, rangeLength, serveBody) = SetHeadersAndLog( + context, + result, + result.FileContents.Length, + result.EnableRangeProcessing, + result.LastModified, + result.EntityTag); + + if (!serveBody) + { + return Task.CompletedTask; + } + + return WriteFileAsync(context, result, range, rangeLength); + } + + protected virtual Task WriteFileAsync(ActionContext context, FileContentResult result, RangeItemHeaderValue range, long rangeLength) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (range != null && rangeLength == 0) + { + return Task.CompletedTask; + } + + if (range != null) + { + Logger.WritingRangeToBody(); + } + + var fileContentStream = new MemoryStream(result.FileContents); + return WriteFileAsync(context.HttpContext, fileContentStream, range, rangeLength); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileResultExecutorBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileResultExecutorBase.cs new file mode 100644 index 0000000000..8c2ecb98a2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileResultExecutorBase.cs @@ -0,0 +1,393 @@ +// Copyright (c) .NET 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.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Extensions; +using Microsoft.AspNetCore.Http.Headers; +using Microsoft.AspNetCore.Internal; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class FileResultExecutorBase + { + private const string AcceptRangeHeaderValue = "bytes"; + + protected const int BufferSize = 64 * 1024; + + public FileResultExecutorBase(ILogger logger) + { + Logger = logger; + } + + internal enum PreconditionState + { + Unspecified, + NotModified, + ShouldProcess, + PreconditionFailed + } + + protected ILogger Logger { get; } + + protected virtual (RangeItemHeaderValue range, long rangeLength, bool serveBody) SetHeadersAndLog( + ActionContext context, + FileResult result, + long? fileLength, + bool enableRangeProcessing, + DateTimeOffset? lastModified = null, + EntityTagHeaderValue etag = null) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + SetContentType(context, result); + SetContentDispositionHeader(context, result); + + var request = context.HttpContext.Request; + var httpRequestHeaders = request.GetTypedHeaders(); + + // Since the 'Last-Modified' and other similar http date headers are rounded down to whole seconds, + // round down current file's last modified to whole seconds for correct comparison. + if (lastModified.HasValue) + { + lastModified = RoundDownToWholeSeconds(lastModified.Value); + } + + var preconditionState = GetPreconditionState(httpRequestHeaders, lastModified, etag); + + var response = context.HttpContext.Response; + SetLastModifiedAndEtagHeaders(response, lastModified, etag); + + // Short circuit if the preconditional headers process to 304 (NotModified) or 412 (PreconditionFailed) + if (preconditionState == PreconditionState.NotModified) + { + response.StatusCode = StatusCodes.Status304NotModified; + return (range: null, rangeLength: 0, serveBody: false); + } + else if (preconditionState == PreconditionState.PreconditionFailed) + { + response.StatusCode = StatusCodes.Status412PreconditionFailed; + return (range: null, rangeLength: 0, serveBody: false); + } + + if (fileLength.HasValue) + { + // Assuming the request is not a range request, and the response body is not empty, the Content-Length header is set to + // the length of the entire file. + // If the request is a valid range request, this header is overwritten with the length of the range as part of the + // range processing (see method SetContentLength). + + response.ContentLength = fileLength.Value; + + // Handle range request + if (enableRangeProcessing) + { + SetAcceptRangeHeader(response); + + // If the request method is HEAD or GET, PreconditionState is Unspecified or ShouldProcess, and IfRange header is valid, + // range should be processed and Range headers should be set + if ((HttpMethods.IsHead(request.Method) || HttpMethods.IsGet(request.Method)) + && (preconditionState == PreconditionState.Unspecified || preconditionState == PreconditionState.ShouldProcess) + && (IfRangeValid(httpRequestHeaders, lastModified, etag))) + { + return SetRangeHeaders(context, httpRequestHeaders, fileLength.Value); + } + } + else + { + Logger.NotEnabledForRangeProcessing(); + } + } + + return (range: null, rangeLength: 0, serveBody: !HttpMethods.IsHead(request.Method)); + } + + private static void SetContentType(ActionContext context, FileResult result) + { + var response = context.HttpContext.Response; + response.ContentType = result.ContentType; + } + + private static void SetContentDispositionHeader(ActionContext context, FileResult result) + { + if (!string.IsNullOrEmpty(result.FileDownloadName)) + { + // From RFC 2183, Sec. 2.3: + // The sender may want to suggest a filename to be used if the entity is + // detached and stored in a separate file. If the receiving MUA writes + // the entity to a file, the suggested filename should be used as a + // basis for the actual filename, where possible. + var contentDisposition = new ContentDispositionHeaderValue("attachment"); + contentDisposition.SetHttpFileName(result.FileDownloadName); + context.HttpContext.Response.Headers[HeaderNames.ContentDisposition] = contentDisposition.ToString(); + } + } + + private static void SetLastModifiedAndEtagHeaders(HttpResponse response, DateTimeOffset? lastModified, EntityTagHeaderValue etag) + { + var httpResponseHeaders = response.GetTypedHeaders(); + if (lastModified.HasValue) + { + httpResponseHeaders.LastModified = lastModified; + } + if (etag != null) + { + httpResponseHeaders.ETag = etag; + } + } + + private static void SetAcceptRangeHeader(HttpResponse response) + { + response.Headers[HeaderNames.AcceptRanges] = AcceptRangeHeaderValue; + } + + internal bool IfRangeValid( + RequestHeaders httpRequestHeaders, + DateTimeOffset? lastModified = null, + EntityTagHeaderValue etag = null) + { + // 14.27 If-Range + var ifRange = httpRequestHeaders.IfRange; + if (ifRange != null) + { + // If the validator given in the If-Range header field matches the + // current validator for the selected representation of the target + // resource, then the server SHOULD process the Range header field as + // requested. If the validator does not match, the server MUST ignore + // the Range header field. + if (ifRange.LastModified.HasValue) + { + if (lastModified.HasValue && lastModified > ifRange.LastModified) + { + Logger.IfRangeLastModifiedPreconditionFailed(lastModified, ifRange.LastModified); + return false; + } + } + else if (etag != null && ifRange.EntityTag != null && !ifRange.EntityTag.Compare(etag, useStrongComparison: true)) + { + Logger.IfRangeETagPreconditionFailed(etag, ifRange.EntityTag); + return false; + } + } + + return true; + } + + // Internal for testing + internal PreconditionState GetPreconditionState( + RequestHeaders httpRequestHeaders, + DateTimeOffset? lastModified = null, + EntityTagHeaderValue etag = null) + { + var ifMatchState = PreconditionState.Unspecified; + var ifNoneMatchState = PreconditionState.Unspecified; + var ifModifiedSinceState = PreconditionState.Unspecified; + var ifUnmodifiedSinceState = PreconditionState.Unspecified; + + // 14.24 If-Match + var ifMatch = httpRequestHeaders.IfMatch; + if (etag != null) + { + ifMatchState = GetEtagMatchState( + useStrongComparison: true, + etagHeader: ifMatch, + etag: etag, + matchFoundState: PreconditionState.ShouldProcess, + matchNotFoundState: PreconditionState.PreconditionFailed); + + if (ifMatchState == PreconditionState.PreconditionFailed) + { + Logger.IfMatchPreconditionFailed(etag); + } + } + + // 14.26 If-None-Match + var ifNoneMatch = httpRequestHeaders.IfNoneMatch; + if (etag != null) + { + ifNoneMatchState = GetEtagMatchState( + useStrongComparison: false, + etagHeader: ifNoneMatch, + etag: etag, + matchFoundState: PreconditionState.NotModified, + matchNotFoundState: PreconditionState.ShouldProcess); + } + + var now = RoundDownToWholeSeconds(DateTimeOffset.UtcNow); + + // 14.25 If-Modified-Since + var ifModifiedSince = httpRequestHeaders.IfModifiedSince; + if (lastModified.HasValue && ifModifiedSince.HasValue && ifModifiedSince <= now) + { + var modified = ifModifiedSince < lastModified; + ifModifiedSinceState = modified ? PreconditionState.ShouldProcess : PreconditionState.NotModified; + } + + // 14.28 If-Unmodified-Since + var ifUnmodifiedSince = httpRequestHeaders.IfUnmodifiedSince; + if (lastModified.HasValue && ifUnmodifiedSince.HasValue && ifUnmodifiedSince <= now) + { + var unmodified = ifUnmodifiedSince >= lastModified; + ifUnmodifiedSinceState = unmodified ? PreconditionState.ShouldProcess : PreconditionState.PreconditionFailed; + + if (ifUnmodifiedSinceState == PreconditionState.PreconditionFailed) + { + Logger.IfUnmodifiedSincePreconditionFailed(lastModified, ifUnmodifiedSince); + } + } + + var state = GetMaxPreconditionState(ifMatchState, ifNoneMatchState, ifModifiedSinceState, ifUnmodifiedSinceState); + return state; + } + + private static PreconditionState GetEtagMatchState( + bool useStrongComparison, + IList etagHeader, + EntityTagHeaderValue etag, + PreconditionState matchFoundState, + PreconditionState matchNotFoundState) + { + if (etagHeader != null && etagHeader.Any()) + { + var state = matchNotFoundState; + foreach (var entityTag in etagHeader) + { + if (entityTag.Equals(EntityTagHeaderValue.Any) || entityTag.Compare(etag, useStrongComparison)) + { + state = matchFoundState; + break; + } + } + + return state; + } + + return PreconditionState.Unspecified; + } + + private static PreconditionState GetMaxPreconditionState(params PreconditionState[] states) + { + var max = PreconditionState.Unspecified; + for (var i = 0; i < states.Length; i++) + { + if (states[i] > max) + { + max = states[i]; + } + } + + return max; + } + + private (RangeItemHeaderValue range, long rangeLength, bool serveBody) SetRangeHeaders( + ActionContext context, + RequestHeaders httpRequestHeaders, + long fileLength) + { + var response = context.HttpContext.Response; + var httpResponseHeaders = response.GetTypedHeaders(); + var serveBody = !HttpMethods.IsHead(context.HttpContext.Request.Method); + + // Range may be null for empty range header, invalid ranges, parsing errors, multiple ranges + // and when the file length is zero. + var (isRangeRequest, range) = RangeHelper.ParseRange( + context.HttpContext, + httpRequestHeaders, + fileLength, + Logger); + + if (!isRangeRequest) + { + return (range: null, rangeLength: 0, serveBody); + } + + // Requested range is not satisfiable + if (range == null) + { + // 14.16 Content-Range - A server sending a response with status code 416 (Requested range not satisfiable) + // SHOULD include a Content-Range field with a byte-range-resp-spec of "*". The instance-length specifies + // the current length of the selected resource. e.g. */length + response.StatusCode = StatusCodes.Status416RangeNotSatisfiable; + httpResponseHeaders.ContentRange = new ContentRangeHeaderValue(fileLength); + + return (range: null, rangeLength: 0, serveBody: false); + } + + response.StatusCode = StatusCodes.Status206PartialContent; + httpResponseHeaders.ContentRange = new ContentRangeHeaderValue( + range.From.Value, + range.To.Value, + fileLength); + + // Overwrite the Content-Length header for valid range requests with the range length. + var rangeLength = SetContentLength(response, range); + + return (range, rangeLength, serveBody); + } + + private static long SetContentLength(HttpResponse response, RangeItemHeaderValue range) + { + var start = range.From.Value; + var end = range.To.Value; + var length = end - start + 1; + response.ContentLength = length; + return length; + } + + protected static ILogger CreateLogger(ILoggerFactory factory) + { + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + return factory.CreateLogger(); + } + + protected static async Task WriteFileAsync(HttpContext context, Stream fileStream, RangeItemHeaderValue range, long rangeLength) + { + var outputStream = context.Response.Body; + using (fileStream) + { + try + { + if (range == null) + { + await StreamCopyOperation.CopyToAsync(fileStream, outputStream, count: null, bufferSize: BufferSize, cancel: context.RequestAborted); + } + else + { + fileStream.Seek(range.From.Value, SeekOrigin.Begin); + await StreamCopyOperation.CopyToAsync(fileStream, outputStream, rangeLength, BufferSize, context.RequestAborted); + } + } + catch (OperationCanceledException) + { + // Don't throw this exception, it's most likely caused by the client disconnecting. + // However, if it was cancelled for any other reason we need to prevent empty responses. + context.Abort(); + } + } + } + + private static DateTimeOffset RoundDownToWholeSeconds(DateTimeOffset dateTimeOffset) + { + var ticksToRemove = dateTimeOffset.Ticks % TimeSpan.TicksPerSecond; + return dateTimeOffset.Subtract(TimeSpan.FromTicks(ticksToRemove)); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileStreamResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileStreamResultExecutor.cs new file mode 100644 index 0000000000..6cb300eff1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileStreamResultExecutor.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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class FileStreamResultExecutor : FileResultExecutorBase, IActionResultExecutor + { + public FileStreamResultExecutor(ILoggerFactory loggerFactory) + : base(CreateLogger(loggerFactory)) + { + } + + /// + public virtual async Task ExecuteAsync(ActionContext context, FileStreamResult result) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + using (result.FileStream) + { + Logger.ExecutingFileResult(result); + + long? fileLength = null; + if (result.FileStream.CanSeek) + { + fileLength = result.FileStream.Length; + } + + var (range, rangeLength, serveBody) = SetHeadersAndLog( + context, + result, + fileLength, + result.EnableRangeProcessing, + result.LastModified, + result.EntityTag); + + if (!serveBody) + { + return; + } + + await WriteFileAsync(context, result, range, rangeLength); + } + } + + protected virtual Task WriteFileAsync( + ActionContext context, + FileStreamResult result, + RangeItemHeaderValue range, + long rangeLength) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (range != null && rangeLength == 0) + { + return Task.CompletedTask; + } + + if (range != null) + { + Logger.WritingRangeToBody(); + } + + return WriteFileAsync(context.HttpContext, result.FileStream, range, rangeLength); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionContextAccessor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionContextAccessor.cs new file mode 100644 index 0000000000..f113257439 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionContextAccessor.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 Microsoft.AspNetCore.Mvc.Infrastructure +{ + public interface IActionContextAccessor + { + ActionContext ActionContext { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorChangeProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorChangeProvider.cs new file mode 100644 index 0000000000..d736512f1c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorChangeProvider.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.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Provides a way to signal invalidation of the cached collection of from an + /// . + /// + public interface IActionDescriptorChangeProvider + { + /// + /// Gets a used to signal invalidation of cached + /// instances. + /// + /// The . + IChangeToken GetChangeToken(); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs new file mode 100644 index 0000000000..3e64a66677 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.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. + +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. + /// + /// 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 + { + /// + /// Returns the current cached + /// + ActionDescriptorCollection ActionDescriptors { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionInvokerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionInvokerFactory.cs new file mode 100644 index 0000000000..9ba0572416 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionInvokerFactory.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.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Defines an interface for creating an for the current request. + /// + /// + /// The default implementation creates an by + /// calling into each . See for more + /// details. + /// + public interface IActionInvokerFactory + { + /// + /// Creates an for the current request associated with + /// . + /// + /// + /// The associated with the current request. + /// + /// An or null. + IActionInvoker CreateInvoker(ActionContext actionContext); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultExecutor.cs new file mode 100644 index 0000000000..3a4e398c5e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultExecutor.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.Threading.Tasks; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Defines an interface for a service which can execute a particular kind of by + /// manipulating the . + /// + /// The type of . + /// + /// Implementions 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 . + /// + /// The associated with the current request."/> + /// The action result to execute. + /// A which represents the asynchronous operation. + Task ExecuteAsync(ActionContext context, TResult result); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultTypeMapper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultTypeMapper.cs new file mode 100644 index 0000000000..1f45bfbb5a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultTypeMapper.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Provides a mapping from the return value of an action to an + /// for request processing. + /// + /// + /// The default implementation of this service handles the conversion of + /// to an during request + /// processing as well as the mapping of to TValue + /// during API Explorer processing. + /// + public interface IActionResultTypeMapper + { + /// + /// Gets the result data type that corresponds to . This + /// method will not be called for actions that return void or an + /// type. + /// + /// The declared return type of an action. + /// A that represents the response data. + /// + /// Prior to calling this method, the infrastructure will unwrap or + /// other task-like types. + /// + Type GetResultDataType(Type returnType); + + /// + /// Converts the result of an action to an for response processing. + /// This method will be not be called when a method returns void or an + /// value. + /// + /// The action return value. May be null. + /// The declared return type. + /// An for response processing. + /// + /// Prior to calling this method, the infrastructure will unwrap or + /// other task-like types. + /// + IActionResult Convert(object value, Type returnType); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionSelector.cs new file mode 100644 index 0000000000..30c9902e7d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionSelector.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Defines an interface for selecting an MVC action to invoke for the current request. + /// + public interface IActionSelector + { + /// + /// Selects a set of candidates for the current request associated with + /// . + /// + /// The associated with the current request. + /// A set of candidates or null. + /// + /// + /// Used by conventional routing to select the set of actions that match the route values for the + /// current request. Action constraints associated with the candidates are not invoked by this method + /// + /// + /// Attribute routing does not call this method. + /// + /// + IReadOnlyList SelectCandidates(RouteContext context); + + /// + /// Selects the best candidate from for the + /// current request associated with . + /// + /// The associated with the current request. + /// The set of candidates. + /// The best candidate for the current request or null. + /// + /// Thrown when action selection results in an ambiguity. + /// + /// + /// + /// Invokes action constraints associated with the candidates. + /// + /// + /// Used by conventional routing after calling to apply action constraints and + /// disambiguate between multiple candidates. + /// + /// + /// Used by attribute routing to apply action constraints and disambiguate between multiple candidates. + /// + /// + ActionDescriptor SelectBestCandidate(RouteContext context, IReadOnlyList candidates); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ICompatibilitySwitch.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ICompatibilitySwitch.cs new file mode 100644 index 0000000000..6f723db0e2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ICompatibilitySwitch.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.Infrastructure +{ + /// + /// Defines a compatibility switch. This is framework infrastructure and should not be used + /// by application code. + /// + public interface ICompatibilitySwitch + { + /// + /// Gets a value indicating whether the property has been set. + /// + /// + /// This is used by the compatibility infrastructure to determine whether the application developer + /// has set explicitly set the value associated with this switch. + /// + bool IsValueSet { get; } + + /// + /// Gets the name of the compatibility switch. + /// + string Name { get; } + + /// + /// Gets or set the value associated with the compatibility switch. + /// + /// + /// Setting the switch value using will not set to true. + /// This should be used by the compatibility infrastructure when is false + /// to apply a compatibility value based on . + /// + object Value { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IConvertToActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IConvertToActionResult.cs new file mode 100644 index 0000000000..8b3d8345f2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IConvertToActionResult.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 +{ + /// + /// Defines the contract to convert a type to an during action invocation. + /// + public interface IConvertToActionResult + { + /// + /// Converts the current instance to an instance of . + /// + /// The converted . + IActionResult Convert(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpRequestStreamReaderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpRequestStreamReaderFactory.cs new file mode 100644 index 0000000000..b29db5163c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpRequestStreamReaderFactory.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.IO; +using System.Text; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Creates instances for reading from . + /// + public interface IHttpRequestStreamReaderFactory + { + /// + /// Creates a new . + /// + /// The , usually . + /// The , usually . + /// A . + TextReader CreateReader(Stream stream, Encoding encoding); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpResponseStreamWriterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpResponseStreamWriterFactory.cs new file mode 100644 index 0000000000..99e2b58528 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpResponseStreamWriterFactory.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.IO; +using System.Text; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Creates instances for writing to . + /// + public interface IHttpResponseStreamWriterFactory + { + /// + /// Creates a new . + /// + /// The , usually . + /// The , usually . + /// A . + TextWriter CreateWriter(Stream stream, Encoding encoding); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/LocalRedirectResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/LocalRedirectResultExecutor.cs new file mode 100644 index 0000000000..9ae7a3bda5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/LocalRedirectResultExecutor.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET 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.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class LocalRedirectResultExecutor : IActionResultExecutor + { + private readonly ILogger _logger; + private readonly IUrlHelperFactory _urlHelperFactory; + + public LocalRedirectResultExecutor(ILoggerFactory loggerFactory, IUrlHelperFactory urlHelperFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + if (urlHelperFactory == null) + { + throw new ArgumentNullException(nameof(urlHelperFactory)); + } + + _logger = loggerFactory.CreateLogger(); + _urlHelperFactory = urlHelperFactory; + } + + /// + public virtual Task ExecuteAsync(ActionContext context, LocalRedirectResult result) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context); + + // IsLocalUrl is called to handle Urls starting with '~/'. + if (!urlHelper.IsLocalUrl(result.Url)) + { + throw new InvalidOperationException(Resources.UrlNotLocal); + } + + var destinationUrl = urlHelper.Content(result.Url); + _logger.LocalRedirectResultExecuting(destinationUrl); + + if (result.PreserveMethod) + { + context.HttpContext.Response.StatusCode = result.Permanent ? + StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect; + context.HttpContext.Response.Headers[HeaderNames.Location] = destinationUrl; + } + else + { + context.HttpContext.Response.Redirect(destinationUrl, result.Permanent); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilter.cs new file mode 100644 index 0000000000..43aa96daec --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilter.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET 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.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// A that responds to invalid . This filter is + /// added to all types and actions annotated with . + /// See for ways to configure this filter. + /// + public class ModelStateInvalidFilter : IActionFilter, IOrderedFilter + { + private readonly ApiBehaviorOptions _apiBehaviorOptions; + private readonly ILogger _logger; + + public ModelStateInvalidFilter(ApiBehaviorOptions apiBehaviorOptions, ILogger logger) + { + _apiBehaviorOptions = apiBehaviorOptions ?? throw new ArgumentNullException(nameof(apiBehaviorOptions)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + /// + /// Gets the order value for determining the order of execution of filters. Filters execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Filters are executed in a sequence determined by an ascending sort of the property. + /// + /// + /// The default Order for this attribute is -2000 so that it runs early in the pipeline. + /// + /// + /// Look at for more detailed info. + /// + /// + public int Order => -2000; + + /// + public bool IsReusable => true; + + public void OnActionExecuted(ActionExecutedContext context) + { + } + + public void OnActionExecuting(ActionExecutingContext context) + { + if (context.Result == null && !context.ModelState.IsValid) + { + _logger.ModelStateInvalidFilterExecuting(); + context.Result = _apiBehaviorOptions.InvalidModelStateResponseFactory(context); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCompatibilityOptions.cs new file mode 100644 index 0000000000..fb78662ed8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCompatibilityOptions.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.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// An options type for configuring the application . + /// + /// + /// The primary way to configure the application's is by + /// calling + /// or . + /// + public class MvcCompatibilityOptions + { + /// + /// Gets or sets the application's configured . + /// + public CompatibilityVersion CompatibilityVersion { get; set; } = CompatibilityVersion.Version_2_0; + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs new file mode 100644 index 0000000000..71957b40cf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.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 Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + internal class MvcOptionsConfigureCompatibilityOptions : ConfigureCompatibilityOptions + { + public MvcOptionsConfigureCompatibilityOptions( + ILoggerFactory loggerFactory, + IOptions compatibilityOptions) + : base(loggerFactory, compatibilityOptions) + { + } + + protected override IReadOnlyDictionary DefaultValues + { + get + { + var values = new Dictionary(); + + if (Version >= CompatibilityVersion.Version_2_1) + { + values[nameof(MvcOptions.AllowCombiningAuthorizeFilters)] = true; + values[nameof(MvcOptions.AllowBindingHeaderValuesToNonStringModelTypes)] = true; + values[nameof(MvcOptions.AllowValidatingTopLevelNodes)] = true; + values[nameof(MvcOptions.InputFormatterExceptionPolicy)] = InputFormatterExceptionPolicy.MalformedInputExceptions; + values[nameof(MvcOptions.SuppressBindingUndefinedValueToEnumType)] = true; + } + + return values; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ObjectResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ObjectResultExecutor.cs new file mode 100644 index 0000000000..5d5fd81b23 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ObjectResultExecutor.cs @@ -0,0 +1,134 @@ +// Copyright (c) .NET 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.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Executes an to write to the response. + /// + public class ObjectResultExecutor : IActionResultExecutor + { + /// + /// Creates a new . + /// + /// The . + /// The . + /// The . + public ObjectResultExecutor( + OutputFormatterSelector formatterSelector, + IHttpResponseStreamWriterFactory writerFactory, + ILoggerFactory loggerFactory) + { + if (formatterSelector == null) + { + throw new ArgumentNullException(nameof(formatterSelector)); + } + + if (writerFactory == null) + { + throw new ArgumentNullException(nameof(writerFactory)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + FormatterSelector = formatterSelector; + WriterFactory = writerFactory.CreateWriter; + Logger = loggerFactory.CreateLogger(); + } + + /// + /// Gets the . + /// + protected ILogger Logger { get; } + + /// + /// Gets the . + /// + protected OutputFormatterSelector FormatterSelector { get; } + + /// + /// Gets the writer factory delegate. + /// + protected Func WriterFactory { get; } + + /// + /// Executes the . + /// + /// The for the current request. + /// The . + /// + /// A which will complete once the is written to the response. + /// + public virtual Task ExecuteAsync(ActionContext context, ObjectResult result) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + 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); + } + } + + var objectType = result.DeclaredType; + if (objectType == null || objectType == typeof(object)) + { + objectType = result.Value?.GetType(); + } + + var formatterContext = new OutputFormatterWriteContext( + context.HttpContext, + WriterFactory, + objectType, + result.Value); + + var selectedFormatter = FormatterSelector.SelectFormatter( + formatterContext, + (IList)result.Formatters ?? Array.Empty(), + result.ContentTypes); + if (selectedFormatter == null) + { + // No formatter supports this. + Logger.NoFormatter(formatterContext); + + context.HttpContext.Response.StatusCode = StatusCodes.Status406NotAcceptable; + return Task.CompletedTask; + } + + Logger.ObjectResultExecuting(result.Value); + + result.OnFormatting(context); + return selectedFormatter.WriteAsync(formatterContext); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/OutputFormatterSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/OutputFormatterSelector.cs new file mode 100644 index 0000000000..fd721d3451 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/OutputFormatterSelector.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; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Selects an to write a response to the current request. + /// + /// + /// + /// The default implementation of provided by ASP.NET Core MVC + /// is . The implements + /// MVC's default content negotiation algorithm. This API is designed in a way that can satisfy the contract + /// of . + /// + /// + /// The default implementation is controlled by settings on , most notably: + /// , , and + /// . + /// + /// + public abstract class OutputFormatterSelector + { + /// + /// Selects an to write the response based on the provided values and the current request. + /// + /// The associated with the current request. + /// A list of formatters to use; this acts as an override to . + /// A list of media types to use; this acts as an override to the Accept header. + /// The selected , or null if one could not be selected. + public abstract IOutputFormatter SelectFormatter(OutputFormatterCanWriteContext context, IList formatters, MediaTypeCollection mediaTypes); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/PhysicalFileResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/PhysicalFileResultExecutor.cs new file mode 100644 index 0000000000..fa4757212d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/PhysicalFileResultExecutor.cs @@ -0,0 +1,148 @@ +// Copyright (c) .NET 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.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class PhysicalFileResultExecutor : FileResultExecutorBase, IActionResultExecutor + { + public PhysicalFileResultExecutor(ILoggerFactory loggerFactory) + : base(CreateLogger(loggerFactory)) + { + } + + /// + public virtual Task ExecuteAsync(ActionContext context, PhysicalFileResult result) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var fileInfo = GetFileInfo(result.FileName); + if (!fileInfo.Exists) + { + throw new FileNotFoundException( + Resources.FormatFileResult_InvalidPath(result.FileName), result.FileName); + } + + Logger.ExecutingFileResult(result, result.FileName); + + var lastModified = result.LastModified ?? fileInfo.LastModified; + var (range, rangeLength, serveBody) = SetHeadersAndLog( + context, + result, + fileInfo.Length, + result.EnableRangeProcessing, + lastModified, + result.EntityTag); + + if (serveBody) + { + return WriteFileAsync(context, result, range, rangeLength); + } + + return Task.CompletedTask; + } + + protected virtual Task WriteFileAsync(ActionContext context, PhysicalFileResult result, RangeItemHeaderValue range, long rangeLength) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (range != null && rangeLength == 0) + { + return Task.CompletedTask; + } + + var response = context.HttpContext.Response; + if (!Path.IsPathRooted(result.FileName)) + { + throw new NotSupportedException(Resources.FormatFileResult_PathNotRooted(result.FileName)); + } + + if (range != null) + { + Logger.WritingRangeToBody(); + } + + var sendFile = response.HttpContext.Features.Get(); + if (sendFile != null) + { + if (range != null) + { + return sendFile.SendFileAsync( + result.FileName, + offset: range.From ?? 0L, + count: rangeLength, + cancellation: default(CancellationToken)); + } + + return sendFile.SendFileAsync( + result.FileName, + offset: 0, + count: null, + cancellation: default(CancellationToken)); + } + + return WriteFileAsync(context.HttpContext, GetFileStream(result.FileName), range, rangeLength); + } + + protected virtual Stream GetFileStream(string path) + { + if (path == null) + { + throw new ArgumentNullException(nameof(path)); + } + + return new FileStream( + path, + FileMode.Open, + FileAccess.Read, + FileShare.ReadWrite, + BufferSize, + FileOptions.Asynchronous | FileOptions.SequentialScan); + } + + protected virtual FileMetadata GetFileInfo(string path) + { + var fileInfo = new FileInfo(path); + return new FileMetadata + { + Exists = fileInfo.Exists, + Length = fileInfo.Length, + LastModified = fileInfo.LastWriteTimeUtc, + }; + } + + protected class FileMetadata + { + public bool Exists { get; set; } + + public long Length { get; set; } + + public DateTimeOffset LastModified { get; set; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectResultExecutor.cs new file mode 100644 index 0000000000..e8f80b17da --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectResultExecutor.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 System; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class RedirectResultExecutor : IActionResultExecutor + { + private readonly ILogger _logger; + private readonly IUrlHelperFactory _urlHelperFactory; + + public RedirectResultExecutor(ILoggerFactory loggerFactory, IUrlHelperFactory urlHelperFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + if (urlHelperFactory == null) + { + throw new ArgumentNullException(nameof(urlHelperFactory)); + } + + _logger = loggerFactory.CreateLogger(); + _urlHelperFactory = urlHelperFactory; + } + + /// + public virtual Task ExecuteAsync(ActionContext context, RedirectResult result) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context); + + // IsLocalUrl is called to handle URLs starting with '~/'. + var destinationUrl = result.Url; + if (urlHelper.IsLocalUrl(destinationUrl)) + { + destinationUrl = urlHelper.Content(result.Url); + } + + _logger.RedirectResultExecuting(destinationUrl); + + if (result.PreserveMethod) + { + context.HttpContext.Response.StatusCode = result.Permanent ? + StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect; + context.HttpContext.Response.Headers[HeaderNames.Location] = destinationUrl; + } + else + { + context.HttpContext.Response.Redirect(destinationUrl, result.Permanent); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToActionResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToActionResultExecutor.cs new file mode 100644 index 0000000000..b1b131274b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToActionResultExecutor.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET 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.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class RedirectToActionResultExecutor : IActionResultExecutor + { + private readonly ILogger _logger; + private readonly IUrlHelperFactory _urlHelperFactory; + + public RedirectToActionResultExecutor(ILoggerFactory loggerFactory, IUrlHelperFactory urlHelperFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + if (urlHelperFactory == null) + { + throw new ArgumentNullException(nameof(urlHelperFactory)); + } + + _logger = loggerFactory.CreateLogger(); + _urlHelperFactory = urlHelperFactory; + } + + /// + public virtual Task ExecuteAsync(ActionContext context, RedirectToActionResult result) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context); + + var destinationUrl = urlHelper.Action( + result.ActionName, + result.ControllerName, + result.RouteValues, + protocol: null, + host: null, + fragment: result.Fragment); + if (string.IsNullOrEmpty(destinationUrl)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + _logger.RedirectToActionResultExecuting(destinationUrl); + + if (result.PreserveMethod) + { + context.HttpContext.Response.StatusCode = result.Permanent ? + StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect; + context.HttpContext.Response.Headers[HeaderNames.Location] = destinationUrl; + } + else + { + context.HttpContext.Response.Redirect(destinationUrl, result.Permanent); + } + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToPageResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToPageResultExecutor.cs new file mode 100644 index 0000000000..ee1c060e87 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToPageResultExecutor.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET 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.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class RedirectToPageResultExecutor : IActionResultExecutor + { + private readonly ILogger _logger; + private readonly IUrlHelperFactory _urlHelperFactory; + + public RedirectToPageResultExecutor(ILoggerFactory loggerFactory, IUrlHelperFactory urlHelperFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + if (urlHelperFactory == null) + { + throw new ArgumentNullException(nameof(urlHelperFactory)); + } + + _logger = loggerFactory.CreateLogger(); + _urlHelperFactory = urlHelperFactory; + } + + /// + public virtual Task ExecuteAsync(ActionContext context, RedirectToPageResult result) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context); + var destinationUrl = urlHelper.Page( + result.PageName, + result.PageHandler, + result.RouteValues, + result.Protocol, + result.Host, + fragment: result.Fragment); + + if (string.IsNullOrEmpty(destinationUrl)) + { + throw new InvalidOperationException(Resources.FormatNoRoutesMatchedForPage(result.PageName)); + } + + _logger.RedirectToPageResultExecuting(result.PageName); + + if (result.PreserveMethod) + { + context.HttpContext.Response.StatusCode = result.Permanent ? + StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect; + context.HttpContext.Response.Headers[HeaderNames.Location] = destinationUrl; + } + else + { + context.HttpContext.Response.Redirect(destinationUrl, result.Permanent); + } + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToRouteResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToRouteResultExecutor.cs new file mode 100644 index 0000000000..04e15feacf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToRouteResultExecutor.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.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class RedirectToRouteResultExecutor : IActionResultExecutor + { + private readonly ILogger _logger; + private readonly IUrlHelperFactory _urlHelperFactory; + + public RedirectToRouteResultExecutor(ILoggerFactory loggerFactory, IUrlHelperFactory urlHelperFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + if (urlHelperFactory == null) + { + throw new ArgumentNullException(nameof(urlHelperFactory)); + } + + _logger = loggerFactory.CreateLogger(); + _urlHelperFactory = urlHelperFactory; + } + + /// + public virtual Task ExecuteAsync(ActionContext context, RedirectToRouteResult result) + { + var urlHelper = result.UrlHelper ?? _urlHelperFactory.GetUrlHelper(context); + + var destinationUrl = urlHelper.RouteUrl( + result.RouteName, + result.RouteValues, + protocol: null, + host: null, + fragment: result.Fragment); + if (string.IsNullOrEmpty(destinationUrl)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + _logger.RedirectToRouteResultExecuting(destinationUrl, result.RouteName); + + if (result.PreserveMethod) + { + context.HttpContext.Response.StatusCode = result.Permanent ? + StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect; + context.HttpContext.Response.Headers[HeaderNames.Location] = destinationUrl; + } + else + { + context.HttpContext.Response.Redirect(destinationUrl, result.Permanent); + } + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/VirtualFileResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/VirtualFileResultExecutor.cs new file mode 100644 index 0000000000..d9702a572f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/VirtualFileResultExecutor.cs @@ -0,0 +1,153 @@ +// Copyright (c) .NET 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.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class VirtualFileResultExecutor : FileResultExecutorBase, IActionResultExecutor + { + private readonly IHostingEnvironment _hostingEnvironment; + + public VirtualFileResultExecutor(ILoggerFactory loggerFactory, IHostingEnvironment hostingEnvironment) + : base(CreateLogger(loggerFactory)) + { + if (hostingEnvironment == null) + { + throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + _hostingEnvironment = hostingEnvironment; + } + + /// + public virtual Task ExecuteAsync(ActionContext context, VirtualFileResult result) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var fileInfo = GetFileInformation(result); + if (!fileInfo.Exists) + { + throw new FileNotFoundException( + Resources.FormatFileResult_InvalidPath(result.FileName), result.FileName); + } + + Logger.ExecutingFileResult(result, result.FileName); + + var lastModified = result.LastModified ?? fileInfo.LastModified; + var (range, rangeLength, serveBody) = SetHeadersAndLog( + context, + result, + fileInfo.Length, + result.EnableRangeProcessing, + lastModified, + result.EntityTag); + + if (serveBody) + { + return WriteFileAsync(context, result, fileInfo, range, rangeLength); + } + + return Task.CompletedTask; + } + + protected virtual Task WriteFileAsync(ActionContext context, VirtualFileResult result, IFileInfo fileInfo, RangeItemHeaderValue range, long rangeLength) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (range != null && rangeLength == 0) + { + return Task.CompletedTask; + } + + var response = context.HttpContext.Response; + var physicalPath = fileInfo.PhysicalPath; + + if (range != null) + { + Logger.WritingRangeToBody(); + } + + var sendFile = response.HttpContext.Features.Get(); + if (sendFile != null && !string.IsNullOrEmpty(physicalPath)) + { + if (range != null) + { + return sendFile.SendFileAsync( + physicalPath, + offset: range.From ?? 0L, + count: rangeLength, + cancellation: default(CancellationToken)); + } + + return sendFile.SendFileAsync( + physicalPath, + offset: 0, + count: null, + cancellation: default(CancellationToken)); + } + + return WriteFileAsync(context.HttpContext, GetFileStream(fileInfo), range, rangeLength); + } + + private IFileInfo GetFileInformation(VirtualFileResult result) + { + var fileProvider = GetFileProvider(result); + if (fileProvider is NullFileProvider) + { + throw new InvalidOperationException(Resources.VirtualFileResultExecutor_NoFileProviderConfigured); + } + + var normalizedPath = result.FileName; + if (normalizedPath.StartsWith("~", StringComparison.Ordinal)) + { + normalizedPath = normalizedPath.Substring(1); + } + + var fileInfo = fileProvider.GetFileInfo(normalizedPath); + return fileInfo; + } + + private IFileProvider GetFileProvider(VirtualFileResult result) + { + if (result.FileProvider != null) + { + return result.FileProvider; + } + + result.FileProvider = _hostingEnvironment.WebRootFileProvider; + return result.FileProvider; + } + + protected virtual Stream GetFileStream(IFileInfo fileInfo) + { + return fileInfo.CreateReadStream(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AcceptHeaderParser.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AcceptHeaderParser.cs new file mode 100644 index 0000000000..289e151226 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AcceptHeaderParser.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.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Internal +{ + public static class AcceptHeaderParser + { + public static IList ParseAcceptHeader(IList acceptHeaders) + { + var parsedValues = new List(); + ParseAcceptHeader(acceptHeaders, parsedValues); + + return parsedValues; + } + + public static void ParseAcceptHeader(IList acceptHeaders, IList parsedValues) + { + if (acceptHeaders == null) + { + throw new ArgumentNullException(nameof(acceptHeaders)); + } + + if (parsedValues == null) + { + throw new ArgumentNullException(nameof(parsedValues)); + } + for (var i = 0; i < acceptHeaders.Count; i++) + { + var charIndex = 0; + var value = acceptHeaders[i]; + + while (!string.IsNullOrEmpty(value) && charIndex < value.Length) + { + var startCharIndex = charIndex; + + if (TryParseValue(value, ref charIndex, out var output)) + { + // The entry may not contain an actual value, like Accept: application/json, , */* + if (output.MediaType.HasValue) + { + parsedValues.Add(output); + } + } + + if (charIndex <= startCharIndex) + { + Debug.Fail("ParseAcceptHeader should advance charIndex, this is a bug."); + break; + } + } + } + } + + private static bool TryParseValue(string value, ref int index, out MediaTypeSegmentWithQuality parsedValue) + { + parsedValue = default(MediaTypeSegmentWithQuality); + + // The accept header may be added multiple times to the request/response message. E.g. + // Accept: text/xml; q=1 + // Accept: + // Accept: text/plain; q=0.2 + // In this case, do not fail parsing in case one of the values is the empty string. + if (string.IsNullOrEmpty(value) || (index == value.Length)) + { + return true; + } + + var currentIndex = GetNextNonEmptyOrWhitespaceIndex(value, index, out var separatorFound); + + if (currentIndex == value.Length) + { + index = currentIndex; + return true; + } + + // We deliberately want to ignore media types that we are not capable of parsing. + // This is due to the fact that some browsers will send invalid media types like + // ; q=0.9 or */;q=0.2, etc. + // In this scenario, our recovery action consists of advancing the pointer to the + // next separator and moving on. + // In case we don't find the next separator, we simply advance the cursor to the + // end of the string to signal that we are done parsing. + var result = default(MediaTypeSegmentWithQuality); + var length = 0; + try + { + length = GetMediaTypeWithQualityLength(value, currentIndex, out result); + } + catch + { + length = 0; + } + + if (length == 0) + { + // The parsing failed. + currentIndex = value.IndexOf(',', currentIndex); + if (currentIndex == -1) + { + index = value.Length; + return false; + } + index = currentIndex; + return false; + } + + currentIndex = currentIndex + length; + currentIndex = GetNextNonEmptyOrWhitespaceIndex(value, currentIndex, out separatorFound); + + // If we've not reached the end of the string, then we must have a separator. + // E. g application/json, text/plain <- We must be at ',' otherwise, we've failed parsing. + if (!separatorFound && (currentIndex < value.Length)) + { + index = currentIndex; + return false; + } + + index = currentIndex; + parsedValue = result; + return true; + } + + private static int GetNextNonEmptyOrWhitespaceIndex( + string input, + int startIndex, + out bool separatorFound) + { + Debug.Assert(input != null); + Debug.Assert(startIndex <= input.Length); // it's OK if index == value.Length. + + separatorFound = false; + var current = startIndex + HttpTokenParsingRules.GetWhitespaceLength(input, startIndex); + + if ((current == input.Length) || (input[current] != ',')) + { + return current; + } + + // If we have a separator, skip the separator and all following whitespaces, and + // continue until the current character is neither a separator nor a whitespace. + separatorFound = true; + current++; // skip delimiter. + current = current + HttpTokenParsingRules.GetWhitespaceLength(input, current); + + while ((current < input.Length) && (input[current] == ',')) + { + current++; // skip delimiter. + current = current + HttpTokenParsingRules.GetWhitespaceLength(input, current); + } + + return current; + } + + private static int GetMediaTypeWithQualityLength( + string input, + int start, + out MediaTypeSegmentWithQuality result) + { + result = MediaType.CreateMediaTypeSegmentWithQuality(input, start); + if (result.MediaType.HasValue) + { + return result.MediaType.Length; + } + else + { + return 0; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionAttributeRouteModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionAttributeRouteModel.cs new file mode 100644 index 0000000000..45fe4b2578 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionAttributeRouteModel.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET 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.ApplicationModels; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public static class ActionAttributeRouteModel + { + public static IEnumerable<(AttributeRouteModel route, SelectorModel actionSelector, SelectorModel controllerSelector)> GetAttributeRoutes(ActionModel actionModel) + { + var controllerAttributeRoutes = actionModel.Controller.Selectors + .Where(sm => sm.AttributeRouteModel != null) + .Select(sm => sm.AttributeRouteModel) + .ToList(); + + foreach (var actionSelectorModel in actionModel.Selectors) + { + var actionRouteModel = actionSelectorModel.AttributeRouteModel; + + // We check the action to see if the template allows combination behavior + // (It doesn't start with / or ~/) so that in the case where we have multiple + // [Route] attributes on the controller we don't end up creating multiple + if (actionRouteModel != null && actionRouteModel.IsAbsoluteTemplate) + { + var route = AttributeRouteModel.CombineAttributeRouteModel( + left: null, + right: actionRouteModel); + + yield return (route, actionSelectorModel, null); + } + else if (controllerAttributeRoutes.Count > 0) + { + for (var i = 0; i < actionModel.Controller.Selectors.Count; i++) + { + // We're using the attribute routes from the controller + var controllerSelector = actionModel.Controller.Selectors[i]; + + var route = AttributeRouteModel.CombineAttributeRouteModel( + controllerSelector.AttributeRouteModel, + actionRouteModel); + + yield return (route, actionSelectorModel, controllerSelector); + } + } + + else + { + var route = AttributeRouteModel.CombineAttributeRouteModel( + left: null, + right: actionRouteModel); + + yield return (route, actionSelectorModel, null); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs new file mode 100644 index 0000000000..3ba049a494 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs @@ -0,0 +1,200 @@ +// Copyright (c) .NET 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.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ActionConstraintCache + { + private readonly IActionDescriptorCollectionProvider _collectionProvider; + private readonly IActionConstraintProvider[] _actionConstraintProviders; + + private volatile InnerCache _currentCache; + + public ActionConstraintCache( + IActionDescriptorCollectionProvider collectionProvider, + IEnumerable actionConstraintProviders) + { + _collectionProvider = collectionProvider; + _actionConstraintProviders = actionConstraintProviders.OrderBy(item => item.Order).ToArray(); + } + + private InnerCache CurrentCache + { + get + { + var current = _currentCache; + var actionDescriptors = _collectionProvider.ActionDescriptors; + + if (current == null || current.Version != actionDescriptors.Version) + { + current = new InnerCache(actionDescriptors.Version); + _currentCache = current; + } + + return current; + } + } + + public IReadOnlyList GetActionConstraints(HttpContext httpContext, ActionDescriptor action) + { + var cache = CurrentCache; + + if (cache.Entries.TryGetValue(action, out var entry)) + { + return GetActionConstraintsFromEntry(entry, httpContext, action); + } + + if (action.ActionConstraints == null || action.ActionConstraints.Count == 0) + { + return null; + } + + var items = new List(action.ActionConstraints.Count); + for (var i = 0; i < action.ActionConstraints.Count; i++) + { + items.Add(new ActionConstraintItem(action.ActionConstraints[i])); + } + + ExecuteProviders(httpContext, action, items); + + var actionConstraints = ExtractActionConstraints(items); + + var allActionConstraintsCached = true; + for (var i = 0; i < items.Count; i++) + { + var item = items[i]; + if (!item.IsReusable) + { + item.Constraint = null; + allActionConstraintsCached = false; + } + } + + if (allActionConstraintsCached) + { + entry = new CacheEntry(actionConstraints); + } + else + { + entry = new CacheEntry(items); + } + + cache.Entries.TryAdd(action, entry); + return actionConstraints; + } + + private IReadOnlyList GetActionConstraintsFromEntry(CacheEntry entry, HttpContext httpContext, ActionDescriptor action) + { + Debug.Assert(entry.ActionConstraints != null || entry.Items != null); + + if (entry.ActionConstraints != null) + { + return entry.ActionConstraints; + } + + var items = new List(entry.Items.Count); + for (var i = 0; i < entry.Items.Count; i++) + { + var item = entry.Items[i]; + if (item.IsReusable) + { + items.Add(item); + } + else + { + items.Add(new ActionConstraintItem(item.Metadata)); + } + } + + ExecuteProviders(httpContext, action, items); + + return ExtractActionConstraints(items); + } + + private void ExecuteProviders(HttpContext httpContext, ActionDescriptor action, List items) + { + var context = new ActionConstraintProviderContext(httpContext, action, items); + + for (var i = 0; i < _actionConstraintProviders.Length; i++) + { + _actionConstraintProviders[i].OnProvidersExecuting(context); + } + + for (var i = _actionConstraintProviders.Length - 1; i >= 0; i--) + { + _actionConstraintProviders[i].OnProvidersExecuted(context); + } + } + + private IReadOnlyList ExtractActionConstraints(List items) + { + var count = 0; + for (var i = 0; i < items.Count; i++) + { + if (items[i].Constraint != null) + { + count++; + } + } + + if (count == 0) + { + return null; + } + + var actionConstraints = new IActionConstraint[count]; + var actionConstraintIndex = 0; + for (int i = 0; i < items.Count; i++) + { + var actionConstraint = items[i].Constraint; + if (actionConstraint != null) + { + actionConstraints[actionConstraintIndex++] = actionConstraint; + } + } + + return actionConstraints; + } + + private class InnerCache + { + public InnerCache(int version) + { + Version = version; + } + + public ConcurrentDictionary Entries { get; } = + new ConcurrentDictionary(); + + public int Version { get; } + } + + private struct CacheEntry + { + public CacheEntry(IReadOnlyList actionConstraints) + { + ActionConstraints = actionConstraints; + Items = null; + } + + public CacheEntry(List items) + { + Items = items; + ActionConstraints = null; + } + + public IReadOnlyList ActionConstraints { get; } + + public List Items { get; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionDescriptorCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionDescriptorCollectionProvider.cs new file mode 100644 index 0000000000..49cb219178 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionDescriptorCollectionProvider.cs @@ -0,0 +1,95 @@ +// Copyright (c) .NET 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/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionInvokerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionInvokerFactory.cs new file mode 100644 index 0000000000..b654ea26a8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionInvokerFactory.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.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ActionInvokerFactory : IActionInvokerFactory + { + private readonly IActionInvokerProvider[] _actionInvokerProviders; + + public ActionInvokerFactory(IEnumerable actionInvokerProviders) + { + _actionInvokerProviders = actionInvokerProviders.OrderBy(item => item.Order).ToArray(); + } + + public IActionInvoker CreateInvoker(ActionContext actionContext) + { + var context = new ActionInvokerProviderContext(actionContext); + + foreach (var provider in _actionInvokerProviders) + { + provider.OnProvidersExecuting(context); + } + + for (var i = _actionInvokerProviders.Length - 1; i >= 0; i--) + { + _actionInvokerProviders[i].OnProvidersExecuted(context); + } + + return context.Result; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs new file mode 100644 index 0000000000..28541b3e55 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs @@ -0,0 +1,231 @@ +// Copyright (c) .NET 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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + internal abstract class ActionMethodExecutor + { + private static readonly ActionMethodExecutor[] Executors = new ActionMethodExecutor[] + { + // Executors for sync methods + new VoidResultExecutor(), + new SyncActionResultExecutor(), + new SyncObjectResultExecutor(), + + // Executors for async methods + new AwaitableResultExecutor(), + new TaskResultExecutor(), + new TaskOfIActionResultExecutor(), + new TaskOfActionResultExecutor(), + new AwaitableObjectResultExecutor(), + }; + + public abstract ValueTask Execute( + IActionResultTypeMapper mapper, + ObjectMethodExecutor executor, + object controller, + object[] arguments); + + protected abstract bool CanExecute(ObjectMethodExecutor executor); + + public static ActionMethodExecutor GetExecutor(ObjectMethodExecutor executor) + { + for (var i = 0; i < Executors.Length; i++) + { + if (Executors[i].CanExecute(executor)) + { + return Executors[i]; + } + } + + Debug.Fail("Should not get here"); + throw new Exception(); + } + + // void LogMessage(..) + private class VoidResultExecutor : ActionMethodExecutor + { + public override ValueTask Execute( + IActionResultTypeMapper mapper, + ObjectMethodExecutor executor, + object controller, + object[] arguments) + { + executor.Execute(controller, arguments); + return new ValueTask(new EmptyResult()); + } + + protected override bool CanExecute(ObjectMethodExecutor executor) + => !executor.IsMethodAsync && executor.MethodReturnType == typeof(void); + } + + // IActionResult Post(..) + // CreatedAtResult Put(..) + private class SyncActionResultExecutor : ActionMethodExecutor + { + public override ValueTask Execute( + IActionResultTypeMapper mapper, + ObjectMethodExecutor executor, + object controller, + object[] arguments) + { + var actionResult = (IActionResult)executor.Execute(controller, arguments); + EnsureActionResultNotNull(executor, actionResult); + + return new ValueTask(actionResult); + } + + protected override bool CanExecute(ObjectMethodExecutor executor) + => !executor.IsMethodAsync && typeof(IActionResult).IsAssignableFrom(executor.MethodReturnType); + } + + // Person GetPerson(..) + // object Index(..) + private class SyncObjectResultExecutor : ActionMethodExecutor + { + public override ValueTask Execute( + IActionResultTypeMapper mapper, + ObjectMethodExecutor executor, + object controller, + object[] arguments) + { + // Sync method returning arbitrary object + var returnValue = executor.Execute(controller, arguments); + var actionResult = ConvertToActionResult(mapper, returnValue, executor.MethodReturnType); + return new ValueTask(actionResult); + } + + // Catch-all for sync methods + protected override bool CanExecute(ObjectMethodExecutor executor) => !executor.IsMethodAsync; + } + + // Task SaveState(..) + private class TaskResultExecutor : ActionMethodExecutor + { + public override async ValueTask Execute( + IActionResultTypeMapper mapper, + ObjectMethodExecutor executor, + object controller, + object[] arguments) + { + await (Task)executor.Execute(controller, arguments); + return new EmptyResult(); + } + + protected override bool CanExecute(ObjectMethodExecutor executor) => executor.MethodReturnType == typeof(Task); + } + + // CustomAsync PerformActionAsync(..) + // Custom task-like type with no return value. + private class AwaitableResultExecutor : ActionMethodExecutor + { + public override async ValueTask Execute( + IActionResultTypeMapper mapper, + ObjectMethodExecutor executor, + object controller, + object[] arguments) + { + await executor.ExecuteAsync(controller, arguments); + return new EmptyResult(); + } + + protected override bool CanExecute(ObjectMethodExecutor executor) + { + // Async method returning void + return executor.IsMethodAsync && executor.AsyncResultType == typeof(void); + } + } + + // Task Post(..) + private class TaskOfIActionResultExecutor : ActionMethodExecutor + { + public override async ValueTask Execute( + IActionResultTypeMapper mapper, + ObjectMethodExecutor executor, + object controller, + object[] arguments) + { + // Async method returning Task + // Avoid extra allocations by calling Execute rather than ExecuteAsync and casting to Task. + var returnValue = executor.Execute(controller, arguments); + var actionResult = await (Task)returnValue; + EnsureActionResultNotNull(executor, actionResult); + + return actionResult; + } + + protected override bool CanExecute(ObjectMethodExecutor executor) + => typeof(Task).IsAssignableFrom(executor.MethodReturnType); + } + + // Task DownloadFile(..) + // ValueTask GetViewsAsync(..) + private class TaskOfActionResultExecutor : ActionMethodExecutor + { + public override async ValueTask Execute( + IActionResultTypeMapper mapper, + ObjectMethodExecutor executor, + object controller, + object[] arguments) + { + // Async method returning awaitable-of-IActionResult (e.g., Task) + // We have to use ExecuteAsync because we don't know the awaitable's type at compile time. + var actionResult = (IActionResult)await executor.ExecuteAsync(controller, arguments); + EnsureActionResultNotNull(executor, actionResult); + return actionResult; + } + + protected override bool CanExecute(ObjectMethodExecutor executor) + { + // Async method returning awaitable-of - IActionResult(e.g., Task) + return executor.IsMethodAsync && typeof(IActionResult).IsAssignableFrom(executor.AsyncResultType); + } + } + + // Task GetPerson(..) + // Task GetCustomerAsync(..) + private class AwaitableObjectResultExecutor : ActionMethodExecutor + { + public override async ValueTask Execute( + IActionResultTypeMapper mapper, + ObjectMethodExecutor executor, + object controller, + object[] arguments) + { + // Async method returning awaitable-of-nonvoid + var returnValue = await executor.ExecuteAsync(controller, arguments); + var actionResult = ConvertToActionResult(mapper, returnValue, executor.AsyncResultType); + return actionResult; + } + + protected override bool CanExecute(ObjectMethodExecutor executor) => true; + } + + private static void EnsureActionResultNotNull(ObjectMethodExecutor executor, IActionResult actionResult) + { + if (actionResult == null) + { + var type = executor.AsyncResultType ?? executor.MethodReturnType; + throw new InvalidOperationException(Resources.FormatActionResult_ActionReturnValueCannotBeNull(type)); + } + } + + private IActionResult ConvertToActionResult(IActionResultTypeMapper mapper, object returnValue, Type declaredType) + { + var result = (returnValue as IActionResult) ?? mapper.Convert(returnValue, declaredType); + if (result == null) + { + throw new InvalidOperationException(Resources.FormatActionResult_ActionReturnValueCannotBeNull(declaredType)); + } + + return result; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionResultTypeMapper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionResultTypeMapper.cs new file mode 100644 index 0000000000..82f1d02998 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionResultTypeMapper.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET 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.Internal +{ + public class ActionResultTypeMapper : IActionResultTypeMapper + { + public Type GetResultDataType(Type returnType) + { + if (returnType == null) + { + throw new ArgumentNullException(nameof(returnType)); + } + + if (returnType.IsGenericType && + returnType.GetGenericTypeDefinition() == typeof(ActionResult<>)) + { + return returnType.GetGenericArguments()[0]; + } + + return returnType; + } + + public IActionResult Convert(object value, Type returnType) + { + if (returnType == null) + { + throw new ArgumentNullException(nameof(returnType)); + } + + if (value is IConvertToActionResult converter) + { + return converter.Convert(); + } + + return new ObjectResult(value) + { + DeclaredType = returnType, + }; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs new file mode 100644 index 0000000000..b3d14d1be8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs @@ -0,0 +1,458 @@ +// Copyright (c) .NET 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.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; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A default implementation. + /// + public class ActionSelector : IActionSelector + { + private static readonly IReadOnlyList EmptyActions = Array.Empty(); + + private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; + private readonly ActionConstraintCache _actionConstraintCache; + private readonly ILogger _logger; + + private Cache _cache; + + /// + /// Creates a new . + /// + /// + /// The . + /// + /// The that + /// providers a set of instances. + /// The . + public ActionSelector( + IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, + ActionConstraintCache actionConstraintCache, + ILoggerFactory loggerFactory) + { + _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; + _logger = loggerFactory.CreateLogger(); + _actionConstraintCache = actionConstraintCache; + } + + private Cache Current + { + get + { + var actions = _actionDescriptorCollectionProvider.ActionDescriptors; + var cache = Volatile.Read(ref _cache); + + if (cache != null && cache.Version == actions.Version) + { + return cache; + } + + cache = new Cache(actions); + Volatile.Write(ref _cache, cache); + return cache; + } + } + + public IReadOnlyList SelectCandidates(RouteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var cache = Current; + + // The Cache works based on a string[] of the route values in a pre-calculated order. This code extracts + // those values in the correct order. + var keys = cache.RouteKeys; + var values = new string[keys.Length]; + for (var i = 0; i < keys.Length; i++) + { + context.RouteData.Values.TryGetValue(keys[i], out object value); + + if (value != null) + { + values[i] = value as string ?? Convert.ToString(value); + } + } + + if (cache.OrdinalEntries.TryGetValue(values, out var matchingRouteValues) || + cache.OrdinalIgnoreCaseEntries.TryGetValue(values, out matchingRouteValues)) + { + Debug.Assert(matchingRouteValues != null); + return matchingRouteValues; + } + + _logger.NoActionsMatched(context.RouteData.Values); + return EmptyActions; + } + + public ActionDescriptor SelectBestCandidate(RouteContext context, IReadOnlyList candidates) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (candidates == null) + { + throw new ArgumentNullException(nameof(candidates)); + } + + var matches = EvaluateActionConstraints(context, candidates); + + var finalMatches = SelectBestActions(matches); + if (finalMatches == null || finalMatches.Count == 0) + { + return null; + } + else if (finalMatches.Count == 1) + { + var selectedAction = finalMatches[0]; + + return selectedAction; + } + else + { + var actionNames = string.Join( + Environment.NewLine, + finalMatches.Select(a => a.DisplayName)); + + _logger.AmbiguousActions(actionNames); + + var message = Resources.FormatDefaultActionSelector_AmbiguousActions( + Environment.NewLine, + actionNames); + + throw new AmbiguousActionException(message); + } + } + + /// + /// Returns the set of best matching actions. + /// + /// The set of actions that satisfy all constraints. + /// A list of the best matching actions. + protected virtual IReadOnlyList SelectBestActions(IReadOnlyList actions) + { + return actions; + } + + private IReadOnlyList EvaluateActionConstraints( + RouteContext context, + IReadOnlyList actions) + { + var candidates = new List(); + + // Perf: Avoid allocations + for (var i = 0; i < actions.Count; i++) + { + var action = actions[i]; + var constraints = _actionConstraintCache.GetActionConstraints(context.HttpContext, action); + candidates.Add(new ActionSelectorCandidate(action, constraints)); + } + + var matches = EvaluateActionConstraintsCore(context, candidates, startingOrder: null); + + List results = null; + if (matches != null) + { + results = new List(matches.Count); + // Perf: Avoid allocations + for (var i = 0; i < matches.Count; i++) + { + var candidate = matches[i]; + results.Add(candidate.Action); + } + } + + return results; + } + + private IReadOnlyList EvaluateActionConstraintsCore( + RouteContext context, + IReadOnlyList candidates, + 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 < candidates.Count; i++) + { + var candidate = candidates[i]; + if (candidate.Constraints != null) + { + for (var j = 0; j < candidate.Constraints.Count; j++) + { + var constraint = candidate.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 candidates; + } + + // Since we have a constraint to process, bisect the set of actions into those with and without a + // constraint for the current order. + var actionsWithConstraint = new List(); + var actionsWithoutConstraint = new List(); + + var constraintContext = new ActionConstraintContext(); + constraintContext.Candidates = candidates; + constraintContext.RouteContext = context; + + // Perf: Avoid allocations + for (var i = 0; i < candidates.Count; i++) + { + var candidate = candidates[i]; + var isMatch = true; + var foundMatchingConstraint = false; + + if (candidate.Constraints != null) + { + constraintContext.CurrentCandidate = candidate; + for (var j = 0; j < candidate.Constraints.Count; j++) + { + var constraint = candidate.Constraints[j]; + if (constraint.Order == order) + { + foundMatchingConstraint = true; + + if (!constraint.Accept(constraintContext)) + { + isMatch = false; + _logger.ConstraintMismatch( + candidate.Action.DisplayName, + candidate.Action.Id, + constraint); + break; + } + } + } + } + + if (isMatch && foundMatchingConstraint) + { + actionsWithConstraint.Add(candidate); + } + else if (isMatch) + { + actionsWithoutConstraint.Add(candidate); + } + } + + // If we have matches with constraints, those are better so try to keep processing those + if (actionsWithConstraint.Count > 0) + { + var matches = EvaluateActionConstraintsCore(context, actionsWithConstraint, order); + if (matches?.Count > 0) + { + return matches; + } + } + + // If the set of matches with constraints can't work, then process the set without constraints. + if (actionsWithoutConstraint.Count == 0) + { + return null; + } + else + { + return EvaluateActionConstraintsCore(context, actionsWithoutConstraint, order); + } + } + + // The action selector cache stores a mapping of route-values -> action descriptors for each known set of + // of route-values. We actually build two of these mappings, one for case-sensitive (fast path) and one for + // case-insensitive (slow path). + // + // This is necessary because MVC routing/action-selection is always case-insensitive. So we're going to build + // a case-sensitive dictionary that will behave like the a case-insensitive dictionary when you hit one of the + // canonical entries. When you don't hit a case-sensitive match it will try the case-insensitive dictionary + // so you still get correct behaviors. + // + // The difference here is because while MVC is case-insensitive, doing a case-sensitive comparison is much + // faster. We also expect that most of the URLs we process are canonically-cased because they were generated + // by Url.Action or another routing api. + // + // This means that for a set of actions like: + // { controller = "Home", action = "Index" } -> HomeController::Index1() + // { controller = "Home", action = "index" } -> HomeController::Index2() + // + // Both of these actions match "Index" case-insensitively, but there exist two known canonical casings, + // so we will create an entry for "Index" and an entry for "index". Both of these entries match **both** + // actions. + private class Cache + { + public Cache(ActionDescriptorCollection actions) + { + // We need to store the version so the cache can be invalidated if the actions change. + Version = actions.Version; + + // We need to build two maps for all of the route values. + OrdinalEntries = new Dictionary>(StringArrayComparer.Ordinal); + OrdinalIgnoreCaseEntries = new Dictionary>(StringArrayComparer.OrdinalIgnoreCase); + + // We need to first identify of the keys that action selection will look at (in route data). + // We want to only consider conventionally routed actions here. + var routeKeys = new HashSet(StringComparer.OrdinalIgnoreCase); + for (var i = 0; i < actions.Items.Count; i++) + { + var action = actions.Items[i]; + if (action.AttributeRouteInfo == null) + { + // This is a conventionally routed action - so make sure we include its keys in the set of + // known route value keys. + foreach (var kvp in action.RouteValues) + { + routeKeys.Add(kvp.Key); + } + } + } + + // We need to hold on to an ordered set of keys for the route values. We'll use these later to + // extract the set of route values from an incoming request to compare against our maps of known + // route values. + RouteKeys = routeKeys.ToArray(); + + for (var i = 0; i < actions.Items.Count; i++) + { + var action = actions.Items[i]; + if (action.AttributeRouteInfo != null) + { + // This only handles conventional routing. Ignore attribute routed actions. + continue; + } + + // This is a conventionally routed action - so we need to extract the route values associated + // with this action (in order) so we can store them in our dictionaries. + var routeValues = new string[RouteKeys.Length]; + for (var j = 0; j < RouteKeys.Length; j++) + { + action.RouteValues.TryGetValue(RouteKeys[j], out routeValues[j]); + } + + if (!OrdinalIgnoreCaseEntries.TryGetValue(routeValues, out var entries)) + { + entries = new List(); + OrdinalIgnoreCaseEntries.Add(routeValues, entries); + } + + entries.Add(action); + + // 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. + // + // `entries` will appear in `OrdinalIgnoreCaseEntries` exactly once and in `OrdinalEntries` once + // for each variation of casing that we've seen. + if (!OrdinalEntries.ContainsKey(routeValues)) + { + OrdinalEntries.Add(routeValues, entries); + } + } + } + + public int Version { get; } + + public string[] RouteKeys { get; } + + public Dictionary> OrdinalEntries { get; } + + public Dictionary> OrdinalIgnoreCaseEntries { get; } + } + + private class StringArrayComparer : IEqualityComparer + { + public static readonly StringArrayComparer Ordinal = new StringArrayComparer(StringComparer.Ordinal); + + public static readonly StringArrayComparer OrdinalIgnoreCase = new StringArrayComparer(StringComparer.OrdinalIgnoreCase); + + private readonly StringComparer _valueComparer; + + private StringArrayComparer(StringComparer valueComparer) + { + _valueComparer = valueComparer; + } + + public bool Equals(string[] x, string[] y) + { + if (object.ReferenceEquals(x, y)) + { + return true; + } + + if (x == null ^ y == null) + { + return false; + } + + if (x.Length != y.Length) + { + return false; + } + + for (var i = 0; i < x.Length; i++) + { + if (string.IsNullOrEmpty(x[i]) && string.IsNullOrEmpty(y[i])) + { + continue; + } + + if (!_valueComparer.Equals(x[i], y[i])) + { + return false; + } + } + + return true; + } + + public int GetHashCode(string[] obj) + { + if (obj == null) + { + return 0; + } + + var hash = new HashCodeCombiner(); + for (var i = 0; i < obj.Length; i++) + { + var o = obj[i]; + + // Route values define null and "" to be equivalent. + if (string.IsNullOrEmpty(o)) + { + o = null; + } + hash.Add(o, _valueComparer); + } + + return hash.CombinedHash; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AmbiguousActionException.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AmbiguousActionException.cs new file mode 100644 index 0000000000..6b1a34d893 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AmbiguousActionException.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.Runtime.Serialization; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// An exception which indicates multiple matches in action selection. + /// + [Serializable] + public class AmbiguousActionException : InvalidOperationException + { + public AmbiguousActionException(string message) + : base(message) + { + } + + protected AmbiguousActionException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs new file mode 100644 index 0000000000..1661eefc03 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs @@ -0,0 +1,262 @@ +// Copyright (c) .NET 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 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 ApiBehaviorOptions _apiBehaviorOptions; + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly ModelStateInvalidFilter _modelStateInvalidFilter; + private readonly ILogger _logger; + + public ApiBehaviorApplicationModelProvider( + IOptions apiBehaviorOptions, + IModelMetadataProvider modelMetadataProvider, + 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()); + } + + /// + /// 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); + + foreach (var actionModel in controllerModel.Actions) + { + if (!isApiController && !actionModel.Attributes.OfType().Any()) + { + continue; + } + + EnsureActionIsAttributeRouted(controllerHasSelectorModel, actionModel); + + AddInvalidModelStateFilter(actionModel); + + InferParameterBindingSources(actionModel); + + InferParameterModelPrefixes(actionModel); + + AddMultipartFormDataConsumesAttribute(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); + } + + // 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; + } + + 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/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs new file mode 100644 index 0000000000..48f50f7980 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.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.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ApiBehaviorOptionsSetup : IConfigureOptions + { + + public ApiBehaviorOptionsSetup() + { + } + + public void Configure(ApiBehaviorOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.InvalidModelStateResponseFactory = GetInvalidModelStateResponse; + + IActionResult GetInvalidModelStateResponse(ActionContext context) + { + var result = new BadRequestObjectResult(context.ModelState); + + result.ContentTypes.Add("application/json"); + result.ContentTypes.Add("application/xml"); + + return result; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiDescriptionActionData.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiDescriptionActionData.cs new file mode 100644 index 0000000000..1a24b3d8bd --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiDescriptionActionData.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.Internal +{ + /// + /// Represents data used to build an ApiDescription, stored as part of the + /// . + /// + public class ApiDescriptionActionData + { + /// + /// The ApiDescription.GroupName of ApiDescription objects for the associated + /// action. + /// + public string GroupName { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApplicationModelConventions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApplicationModelConventions.cs new file mode 100644 index 0000000000..108890f5ce --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApplicationModelConventions.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; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// Applies conventions to a . + /// + public static class ApplicationModelConventions + { + /// + /// Applies conventions to a . + /// + /// The . + /// The set of conventions. + public static void ApplyConventions( + ApplicationModel applicationModel, + IEnumerable conventions) + { + if (applicationModel == null) + { + throw new ArgumentNullException(nameof(applicationModel)); + } + + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + // Conventions are applied from the outside-in to allow for scenarios where an action overrides + // a controller, etc. + foreach (var convention in conventions) + { + convention.Apply(applicationModel); + } + + var controllers = applicationModel.Controllers.ToArray(); + // First apply the conventions from attributes in decreasing order of scope. + foreach (var controller in controllers) + { + // ToArray is needed here to prevent issues with modifying the attributes collection + // while iterating it. + var controllerConventions = + controller.Attributes + .OfType() + .ToArray(); + + foreach (var controllerConvention in controllerConventions) + { + controllerConvention.Apply(controller); + } + + var actions = controller.Actions.ToArray(); + foreach (var action in actions) + { + // ToArray is needed here to prevent issues with modifying the attributes collection + // while iterating it. + var actionConventions = + action.Attributes + .OfType() + .ToArray(); + + foreach (var actionConvention in actionConventions) + { + actionConvention.Apply(action); + } + + var parameters = action.Parameters.ToArray(); + foreach (var parameter in parameters) + { + // ToArray is needed here to prevent issues with modifying the attributes collection + // while iterating it. + var parameterConventions = + parameter.Attributes + .OfType() + .ToArray(); + + foreach (var parameterConvention in parameterConventions) + { + parameterConvention.Apply(parameter); + } + + var parameterBaseConventions = GetConventions(conventions, parameter.Attributes); + foreach (var parameterConvention in parameterBaseConventions) + { + parameterConvention.Apply(parameter); + } + } + } + + var properties = controller.ControllerProperties.ToArray(); + foreach (var property in properties) + { + var parameterBaseConventions = GetConventions(conventions, property.Attributes); + + foreach (var parameterConvention in parameterBaseConventions) + { + parameterConvention.Apply(property); + } + } + } + } + + private static IEnumerable GetConventions( + IEnumerable conventions, + IReadOnlyList attributes) + { + return Enumerable.Concat( + conventions.OfType(), + attributes.OfType()); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs new file mode 100644 index 0000000000..bf1f4d575d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs @@ -0,0 +1,317 @@ +// Copyright (c) .NET 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.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; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class AttributeRoute : IRouter + { + private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; + private readonly IServiceProvider _services; + private readonly Func _handlerFactory; + + private TreeRouter _router; + + public AttributeRoute( + IActionDescriptorCollectionProvider actionDescriptorCollectionProvider, + IServiceProvider services, + Func handlerFactory) + { + if (actionDescriptorCollectionProvider == null) + { + throw new ArgumentNullException(nameof(actionDescriptorCollectionProvider)); + } + + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (handlerFactory == null) + { + throw new ArgumentNullException(nameof(handlerFactory)); + } + + _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; + _services = services; + _handlerFactory = handlerFactory; + } + + /// + public VirtualPathData GetVirtualPath(VirtualPathContext context) + { + var router = GetTreeRouter(); + return router.GetVirtualPath(context); + } + + /// + public Task RouteAsync(RouteContext context) + { + var router = GetTreeRouter(); + return router.RouteAsync(context); + } + + private TreeRouter GetTreeRouter() + { + var actions = _actionDescriptorCollectionProvider.ActionDescriptors; + + // This is a safe-race. We'll never set router back to null after initializing + // it on startup. + if (_router == null || _router.Version != actions.Version) + { + var builder = _services.GetRequiredService(); + AddEntries(builder, actions); + _router = builder.Build(actions.Version); + } + + return _router; + } + + // internal for testing + internal void AddEntries(TreeRouteBuilder builder, ActionDescriptorCollection actions) + { + var routeInfos = GetRouteInfos(actions.Items); + + // We're creating one TreeRouteLinkGenerationEntry per action. This allows us to match the intended + // action by expected route values, and then use the TemplateBinder to generate the link. + foreach (var routeInfo in routeInfos) + { + if (routeInfo.SuppressLinkGeneration) + { + continue; + } + + var defaults = new RouteValueDictionary(); + foreach (var kvp in routeInfo.ActionDescriptor.RouteValues) + { + defaults.Add(kvp.Key, kvp.Value); + } + + try + { + // We use the `NullRouter` as the route handler because we don't need to do anything for link + // generations. The TreeRouter does it all for us. + builder.MapOutbound( + NullRouter.Instance, + routeInfo.RouteTemplate, + defaults, + routeInfo.RouteName, + routeInfo.Order); + } + catch (RouteCreationException routeCreationException) + { + throw new RouteCreationException( + "An error occurred while adding a route to the route builder. " + + $"Route name '{routeInfo.RouteName}' and template '{routeInfo.RouteTemplate.TemplateText}'.", + routeCreationException); + } + } + + // We're creating one AttributeRouteMatchingEntry per group, so we need to identify the distinct set of + // groups. It's guaranteed that all members of the group have the same template and precedence, + // so we only need to hang on to a single instance of the RouteInfo for each group. + var groups = GetInboundRouteGroups(routeInfos); + foreach (var group in groups) + { + var handler = _handlerFactory(group.ToArray()); + + // Note that because we only support 'inline' defaults, each routeInfo group also has the same + // set of defaults. + // + // We then inject the route group as a default for the matcher so it gets passed back to MVC + // for use in action selection. + builder.MapInbound( + handler, + group.Key.RouteTemplate, + group.Key.RouteName, + group.Key.Order); + } + } + + private static IEnumerable> GetInboundRouteGroups(List routeInfos) + { + return routeInfos + .Where(routeInfo => !routeInfo.SuppressPathMatching) + .GroupBy(r => r, r => r.ActionDescriptor, RouteInfoEqualityComparer.Instance); + } + + private static List GetRouteInfos(IReadOnlyList actions) + { + var routeInfos = new List(); + var errors = new List(); + + // This keeps a cache of 'Template' objects. It's a fairly common case that multiple actions + // will use the same route template string; thus, the `Template` object can be shared. + // + // For a relatively simple route template, the `Template` object will hold about 500 bytes + // of memory, so sharing is worthwhile. + var templateCache = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var attributeRoutedActions = actions.Where(a => a.AttributeRouteInfo?.Template != null); + foreach (var action in attributeRoutedActions) + { + var routeInfo = GetRouteInfo(templateCache, action); + if (routeInfo.ErrorMessage == null) + { + routeInfos.Add(routeInfo); + } + else + { + errors.Add(routeInfo); + } + } + + if (errors.Count > 0) + { + var allErrors = string.Join( + Environment.NewLine + Environment.NewLine, + errors.Select( + e => Resources.FormatAttributeRoute_IndividualErrorMessage( + e.ActionDescriptor.DisplayName, + Environment.NewLine, + e.ErrorMessage))); + + var message = Resources.FormatAttributeRoute_AggregateErrorMessage(Environment.NewLine, allErrors); + throw new RouteCreationException(message); + } + + return routeInfos; + } + + private static RouteInfo GetRouteInfo( + Dictionary templateCache, + ActionDescriptor action) + { + var routeInfo = new RouteInfo() + { + ActionDescriptor = action, + }; + + try + { + if (!templateCache.TryGetValue(action.AttributeRouteInfo.Template, out var parsedTemplate)) + { + // Parsing with throw if the template is invalid. + parsedTemplate = TemplateParser.Parse(action.AttributeRouteInfo.Template); + templateCache.Add(action.AttributeRouteInfo.Template, parsedTemplate); + } + + routeInfo.RouteTemplate = parsedTemplate; + routeInfo.SuppressPathMatching = action.AttributeRouteInfo.SuppressPathMatching; + routeInfo.SuppressLinkGeneration = action.AttributeRouteInfo.SuppressLinkGeneration; + } + catch (Exception ex) + { + routeInfo.ErrorMessage = ex.Message; + return routeInfo; + } + + foreach (var kvp in action.RouteValues) + { + foreach (var parameter in routeInfo.RouteTemplate.Parameters) + { + if (string.Equals(kvp.Key, parameter.Name, StringComparison.OrdinalIgnoreCase)) + { + routeInfo.ErrorMessage = Resources.FormatAttributeRoute_CannotContainParameter( + routeInfo.RouteTemplate.TemplateText, + kvp.Key, + kvp.Value); + + return routeInfo; + } + } + } + + routeInfo.Order = action.AttributeRouteInfo.Order; + routeInfo.RouteName = action.AttributeRouteInfo.Name; + + return routeInfo; + } + + private class RouteInfo + { + public ActionDescriptor ActionDescriptor { get; set; } + + public string ErrorMessage { get; set; } + + public int Order { get; set; } + + public string RouteName { get; set; } + + public RouteTemplate RouteTemplate { get; set; } + + public bool SuppressPathMatching { get; set; } + + public bool SuppressLinkGeneration { get; set; } + } + + private class RouteInfoEqualityComparer : IEqualityComparer + { + public static readonly RouteInfoEqualityComparer Instance = new RouteInfoEqualityComparer(); + + public bool Equals(RouteInfo x, RouteInfo y) + { + if (x == null && y == null) + { + return true; + } + else if (x == null ^ y == null) + { + return false; + } + else if (x.Order != y.Order) + { + return false; + } + else + { + return string.Equals( + x.RouteTemplate.TemplateText, + y.RouteTemplate.TemplateText, + StringComparison.OrdinalIgnoreCase); + } + } + + public int GetHashCode(RouteInfo obj) + { + if (obj == null) + { + return 0; + } + + var hash = new HashCodeCombiner(); + hash.Add(obj.Order); + hash.Add(obj.RouteTemplate.TemplateText, StringComparer.OrdinalIgnoreCase); + return hash; + } + } + + // Used only to hook up link generation, and it doesn't need to do anything. + private class NullRouter : IRouter + { + public static readonly NullRouter Instance = new NullRouter(); + + public VirtualPathData GetVirtualPath(VirtualPathContext context) + { + return null; + } + + public Task RouteAsync(RouteContext context) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRouting.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRouting.cs new file mode 100644 index 0000000000..e5bf2d5ec4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRouting.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.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public static class AttributeRouting + { + /// + /// Creates an attribute route using the provided services and provided target router. + /// + /// The application services. + /// An attribute route. + public static IRouter CreateAttributeMegaRoute(IServiceProvider services) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + return new AttributeRoute( + services.GetRequiredService(), + services, + actions => + { + var handler = services.GetRequiredService(); + handler.Actions = actions; + return handler; + }); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AuthorizationApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AuthorizationApplicationModelProvider.cs new file mode 100644 index 0000000000..3166c9deb0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AuthorizationApplicationModelProvider.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET 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.Authorization; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Authorization; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class AuthorizationApplicationModelProvider : IApplicationModelProvider + { + private readonly IAuthorizationPolicyProvider _policyProvider; + + public AuthorizationApplicationModelProvider(IAuthorizationPolicyProvider policyProvider) + { + _policyProvider = policyProvider; + } + + public int Order => -1000 + 10; + + public void OnProvidersExecuted(ApplicationModelProviderContext context) + { + // Intentionally empty. + } + + public void OnProvidersExecuting(ApplicationModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + foreach (var controllerModel in context.Result.Controllers) + { + var controllerModelAuthData = controllerModel.Attributes.OfType().ToArray(); + if (controllerModelAuthData.Length > 0) + { + controllerModel.Filters.Add(GetFilter(_policyProvider, controllerModelAuthData)); + } + foreach (var attribute in controllerModel.Attributes.OfType()) + { + controllerModel.Filters.Add(new AllowAnonymousFilter()); + } + + foreach (var actionModel in controllerModel.Actions) + { + var actionModelAuthData = actionModel.Attributes.OfType().ToArray(); + if (actionModelAuthData.Length > 0) + { + actionModel.Filters.Add(GetFilter(_policyProvider, actionModelAuthData)); + } + + foreach (var attribute in actionModel.Attributes.OfType()) + { + actionModel.Filters.Add(new AllowAnonymousFilter()); + } + } + } + } + + public static AuthorizeFilter GetFilter(IAuthorizationPolicyProvider policyProvider, IEnumerable authData) + { + // The default policy provider will make the same policy for given input, so make it only once. + // This will always execute synchronously. + if (policyProvider.GetType() == typeof(DefaultAuthorizationPolicyProvider)) + { + var policy = AuthorizationPolicy.CombineAsync(policyProvider, authData).GetAwaiter().GetResult(); + return new AuthorizeFilter(policy); + } + else + { + return new AuthorizeFilter(policyProvider, authData); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ClientValidatorCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ClientValidatorCache.cs new file mode 100644 index 0000000000..37adde40c2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ClientValidatorCache.cs @@ -0,0 +1,143 @@ +// Copyright (c) .NET 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.Collections.Generic; +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ClientValidatorCache + { + private readonly ConcurrentDictionary _cacheEntries = new ConcurrentDictionary(); + + public IReadOnlyList GetValidators(ModelMetadata metadata, IClientModelValidatorProvider validatorProvider) + { + if (_cacheEntries.TryGetValue(metadata, out var entry)) + { + return GetValidatorsFromEntry(entry, metadata, validatorProvider); + } + + var items = new List(metadata.ValidatorMetadata.Count); + for (var i = 0; i < metadata.ValidatorMetadata.Count; i++) + { + items.Add(new ClientValidatorItem(metadata.ValidatorMetadata[i])); + } + + ExecuteProvider(validatorProvider, metadata, items); + + var validators = ExtractValidators(items); + + var allValidatorsCached = true; + for (var i = 0; i < items.Count; i++) + { + var item = items[i]; + if (!item.IsReusable) + { + item.Validator = null; + allValidatorsCached = false; + } + } + + if (allValidatorsCached) + { + entry = new CacheEntry(validators); + } + else + { + entry = new CacheEntry(items); + } + + _cacheEntries.TryAdd(metadata, entry); + + return validators; + } + + private IReadOnlyList GetValidatorsFromEntry(CacheEntry entry, ModelMetadata metadata, IClientModelValidatorProvider validationProvider) + { + Debug.Assert(entry.Validators != null || entry.Items != null); + + if (entry.Validators != null) + { + return entry.Validators; + } + + var items = new List(entry.Items.Count); + for (var i = 0; i < entry.Items.Count; i++) + { + var item = entry.Items[i]; + if (item.IsReusable) + { + items.Add(item); + } + else + { + items.Add(new ClientValidatorItem(item.ValidatorMetadata)); + } + } + + ExecuteProvider(validationProvider, metadata, items); + + return ExtractValidators(items); + } + + private void ExecuteProvider(IClientModelValidatorProvider validatorProvider, ModelMetadata metadata, List items) + { + var context = new ClientValidatorProviderContext(metadata, items); + + validatorProvider.CreateValidators(context); + } + + private IReadOnlyList ExtractValidators(List items) + { + var count = 0; + for (var i = 0; i < items.Count; i++) + { + if (items[i].Validator != null) + { + count++; + } + } + + if (count == 0) + { + return Array.Empty(); + } + + var validators = new IClientModelValidator[count]; + var clientValidatorIndex = 0; + for (int i = 0; i < items.Count; i++) + { + var validator = items[i].Validator; + if (validator != null) + { + validators[clientValidatorIndex++] = validator; + } + } + + return validators; + } + + private struct CacheEntry + { + public CacheEntry(IReadOnlyList validators) + { + Validators = validators; + Items = null; + } + + public CacheEntry(List items) + { + Items = items; + Validators = null; + } + + public IReadOnlyList Validators { get; } + + public List Items { get; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs new file mode 100644 index 0000000000..18ae818293 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs @@ -0,0 +1,623 @@ +// Copyright (c) .NET 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.Linq; +using System.Reflection; +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; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// Creates instances of from . + /// + public static class ControllerActionDescriptorBuilder + { + // This is the default order for attribute routes whose order calculated from + // the controller model is null. + private const int DefaultAttributeRouteOrder = 0; + + /// + /// Creates instances of from . + /// + /// The . + /// The list of . + public static IList Build(ApplicationModel application) + { + var actions = new List(); + + var methodInfoMap = new MethodToActionMap(); + + var routeTemplateErrors = new List(); + var attributeRoutingConfigurationErrors = new Dictionary(); + + foreach (var controller in application.Controllers) + { + // Only add properties which are explicitly marked to bind. + // The attribute check is required for ModelBinder attribute. + var controllerPropertyDescriptors = controller.ControllerProperties + .Where(p => p.BindingInfo != null) + .Select(CreateParameterDescriptor) + .ToList(); + foreach (var action in controller.Actions) + { + // Controllers with multiple [Route] attributes (or user defined implementation of + // IRouteTemplateProvider) will generate one action descriptor per IRouteTemplateProvider + // instance. + // Actions with multiple [Http*] attributes or other (IRouteTemplateProvider implementations + // have already been identified as different actions during action discovery. + var actionDescriptors = CreateActionDescriptors(application, controller, action); + + foreach (var actionDescriptor in actionDescriptors) + { + actionDescriptor.ControllerName = controller.ControllerName; + actionDescriptor.ControllerTypeInfo = controller.ControllerType; + + AddApiExplorerInfo(actionDescriptor, application, controller, action); + AddRouteValues(actionDescriptor, controller, action); + AddProperties(actionDescriptor, action, controller, application); + + actionDescriptor.BoundProperties = controllerPropertyDescriptors; + + if (IsAttributeRoutedAction(actionDescriptor)) + { + // Replaces tokens like [controller]/[action] in the route template with the actual values + // for this action. + ReplaceAttributeRouteTokens(actionDescriptor, routeTemplateErrors); + } + } + + methodInfoMap.AddToMethodInfo(action, actionDescriptors); + actions.AddRange(actionDescriptors); + } + } + + var actionsByRouteName = new Dictionary>( + StringComparer.OrdinalIgnoreCase); + + // Keeps track of all the methods that we've validated to avoid visiting each action group + // more than once. + var validatedMethods = new HashSet(); + + foreach (var actionDescriptor in actions) + { + if (!validatedMethods.Contains(actionDescriptor.MethodInfo)) + { + ValidateActionGroupConfiguration( + methodInfoMap, + actionDescriptor, + attributeRoutingConfigurationErrors); + + validatedMethods.Add(actionDescriptor.MethodInfo); + } + + var attributeRouteInfo = actionDescriptor.AttributeRouteInfo; + if (attributeRouteInfo?.Name != null) + { + // Build a map of attribute route name to action descriptors to ensure that all + // attribute routes with a given name have the same template. + AddActionToNamedGroup(actionsByRouteName, attributeRouteInfo.Name, actionDescriptor); + } + } + + if (attributeRoutingConfigurationErrors.Any()) + { + var message = CreateAttributeRoutingAggregateErrorMessage( + attributeRoutingConfigurationErrors.Values); + + throw new InvalidOperationException(message); + } + + var namedRoutedErrors = ValidateNamedAttributeRoutedActions(actionsByRouteName); + if (namedRoutedErrors.Any()) + { + var message = CreateAttributeRoutingAggregateErrorMessage(namedRoutedErrors); + throw new InvalidOperationException(message); + } + + if (routeTemplateErrors.Any()) + { + var message = CreateAttributeRoutingAggregateErrorMessage(routeTemplateErrors); + throw new InvalidOperationException(message); + } + + return actions; + } + + private static IList CreateActionDescriptors( + ApplicationModel application, + ControllerModel controller, + ActionModel action) + { + var defaultControllerConstraints = Enumerable.Empty(); + if (controller.Selectors.Count > 0) + { + defaultControllerConstraints = controller.Selectors[0].ActionConstraints + .Where(constraint => !(constraint is IRouteTemplateProvider)); + } + + var actionDescriptors = new List(); + foreach (var result in ActionAttributeRouteModel.GetAttributeRoutes(action)) + { + var actionSelector = result.actionSelector; + var controllerSelector = result.controllerSelector; + + var actionDescriptor = CreateActionDescriptor(action, result.route); + actionDescriptors.Add(actionDescriptor); + AddActionFilters(actionDescriptor, action.Filters, controller.Filters, application.Filters); + + var controllerConstraints = defaultControllerConstraints; + + if (controllerSelector?.AttributeRouteModel?.Attribute is IActionConstraintMetadata actionConstraint) + { + // Use the attribute route as a constraint if the controller selector participated in creating this route. + controllerConstraints = controllerConstraints.Concat(new[] { actionConstraint }); + } + + AddActionConstraints(actionDescriptor, actionSelector, controllerConstraints); + } + + return actionDescriptors; + } + + private static ControllerActionDescriptor CreateActionDescriptor(ActionModel action, AttributeRouteModel routeModel) + { + var parameterDescriptors = new List(); + foreach (var parameter in action.Parameters) + { + var parameterDescriptor = CreateParameterDescriptor(parameter); + parameterDescriptors.Add(parameterDescriptor); + } + + var actionDescriptor = new ControllerActionDescriptor + { + ActionName = action.ActionName, + MethodInfo = action.ActionMethod, + Parameters = parameterDescriptors, + AttributeRouteInfo = CreateAttributeRouteInfo(routeModel), + }; + + return actionDescriptor; + } + + private static ParameterDescriptor CreateParameterDescriptor(ParameterModel parameterModel) + { + var parameterDescriptor = new ControllerParameterDescriptor() + { + Name = parameterModel.ParameterName, + ParameterType = parameterModel.ParameterInfo.ParameterType, + BindingInfo = parameterModel.BindingInfo, + ParameterInfo = parameterModel.ParameterInfo, + }; + + return parameterDescriptor; + } + + private static ParameterDescriptor CreateParameterDescriptor(PropertyModel propertyModel) + { + var parameterDescriptor = new ControllerBoundPropertyDescriptor() + { + BindingInfo = propertyModel.BindingInfo, + Name = propertyModel.PropertyName, + ParameterType = propertyModel.PropertyInfo.PropertyType, + PropertyInfo = propertyModel.PropertyInfo, + }; + + return parameterDescriptor; + } + + private static void AddApiExplorerInfo( + ControllerActionDescriptor actionDescriptor, + ApplicationModel application, + ControllerModel controller, + ActionModel action) + { + var isVisible = + action.ApiExplorer?.IsVisible ?? + controller.ApiExplorer?.IsVisible ?? + application.ApiExplorer?.IsVisible ?? + false; + + var isVisibleSetOnActionOrController = + action.ApiExplorer?.IsVisible ?? + controller.ApiExplorer?.IsVisible ?? + false; + + // ApiExplorer isn't supported on conventional-routed actions, but we still allow you to configure + // it at the application level when you have a mix of controller types. We'll just skip over enabling + // ApiExplorer for conventional-routed controllers when this happens. + var isVisibleSetOnApplication = application.ApiExplorer?.IsVisible ?? false; + + if (isVisibleSetOnActionOrController && !IsAttributeRoutedAction(actionDescriptor)) + { + // ApiExplorer is only supported on attribute routed actions. + throw new InvalidOperationException(Resources.FormatApiExplorer_UnsupportedAction( + actionDescriptor.DisplayName)); + } + else if (isVisibleSetOnApplication && !IsAttributeRoutedAction(actionDescriptor)) + { + // This is the case where we're going to be lenient, just ignore it. + } + else if (isVisible) + { + Debug.Assert(IsAttributeRoutedAction(actionDescriptor)); + + var apiExplorerActionData = new ApiDescriptionActionData() + { + GroupName = action.ApiExplorer?.GroupName ?? controller.ApiExplorer?.GroupName, + }; + + actionDescriptor.SetProperty(apiExplorerActionData); + } + } + + private static void AddProperties( + ControllerActionDescriptor actionDescriptor, + ActionModel action, + ControllerModel controller, + ApplicationModel application) + { + foreach (var item in application.Properties) + { + actionDescriptor.Properties[item.Key] = item.Value; + } + + foreach (var item in controller.Properties) + { + actionDescriptor.Properties[item.Key] = item.Value; + } + + foreach (var item in action.Properties) + { + actionDescriptor.Properties[item.Key] = item.Value; + } + } + + private static void AddActionFilters( + ControllerActionDescriptor actionDescriptor, + IEnumerable actionFilters, + IEnumerable controllerFilters, + IEnumerable globalFilters) + { + actionDescriptor.FilterDescriptors = + actionFilters.Select(f => new FilterDescriptor(f, FilterScope.Action)) + .Concat(controllerFilters.Select(f => new FilterDescriptor(f, FilterScope.Controller))) + .Concat(globalFilters.Select(f => new FilterDescriptor(f, FilterScope.Global))) + .OrderBy(d => d, FilterDescriptorOrderComparer.Comparer) + .ToList(); + } + + private static AttributeRouteInfo CreateAttributeRouteInfo(AttributeRouteModel routeModel) + { + if (routeModel == null) + { + return null; + } + + return new AttributeRouteInfo + { + Template = routeModel.Template, + Order = routeModel.Order ?? DefaultAttributeRouteOrder, + Name = routeModel.Name, + SuppressLinkGeneration = routeModel.SuppressLinkGeneration, + SuppressPathMatching = routeModel.SuppressPathMatching, + }; + } + + private static void AddActionConstraints( + ControllerActionDescriptor actionDescriptor, + SelectorModel selectorModel, + IEnumerable controllerConstraints) + { + var constraints = new List(); + + if (selectorModel.ActionConstraints != null) + { + constraints.AddRange(selectorModel.ActionConstraints); + } + + if (controllerConstraints != null) + { + constraints.AddRange(controllerConstraints); + } + + if (constraints.Count > 0) + { + actionDescriptor.ActionConstraints = constraints; + } + } + + public static void AddRouteValues( + ControllerActionDescriptor actionDescriptor, + ControllerModel controller, + ActionModel action) + { + // Apply all the constraints defined on the action, then controller (for example, [Area]) + // to the actions. Also keep track of all the constraints that require preventing actions + // without the constraint to match. For example, actions without an [Area] attribute on their + // controller should not match when a value has been given for area when matching a url or + // generating a link. + foreach (var kvp in action.RouteValues) + { + // Skip duplicates + if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key)) + { + actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value); + } + } + + foreach (var kvp in controller.RouteValues) + { + // Skip duplicates - this also means that a value on the action will take precedence + if (!actionDescriptor.RouteValues.ContainsKey(kvp.Key)) + { + actionDescriptor.RouteValues.Add(kvp.Key, kvp.Value); + } + } + + // Lastly add the 'default' values + if (!actionDescriptor.RouteValues.ContainsKey("action")) + { + actionDescriptor.RouteValues.Add("action", action.ActionName ?? string.Empty); + } + + if (!actionDescriptor.RouteValues.ContainsKey("controller")) + { + actionDescriptor.RouteValues.Add("controller", controller.ControllerName); + } + } + + private static void ReplaceAttributeRouteTokens( + ControllerActionDescriptor actionDescriptor, + IList routeTemplateErrors) + { + try + { + actionDescriptor.AttributeRouteInfo.Template = AttributeRouteModel.ReplaceTokens( + actionDescriptor.AttributeRouteInfo.Template, + actionDescriptor.RouteValues); + + if (actionDescriptor.AttributeRouteInfo.Name != null) + { + actionDescriptor.AttributeRouteInfo.Name = AttributeRouteModel.ReplaceTokens( + actionDescriptor.AttributeRouteInfo.Name, + actionDescriptor.RouteValues); + } + } + catch (InvalidOperationException ex) + { + // Routing will throw an InvalidOperationException here if we can't parse/replace tokens + // in the template. + var message = Resources.FormatAttributeRoute_IndividualErrorMessage( + actionDescriptor.DisplayName, + Environment.NewLine, + ex.Message); + + routeTemplateErrors.Add(message); + } + } + + private static void AddActionToNamedGroup( + IDictionary> actionsByRouteName, + string routeName, + ControllerActionDescriptor actionDescriptor) + { + if (actionsByRouteName.TryGetValue(routeName, out var namedActionGroup)) + { + namedActionGroup.Add(actionDescriptor); + } + else + { + namedActionGroup = new List(); + namedActionGroup.Add(actionDescriptor); + actionsByRouteName.Add(routeName, namedActionGroup); + } + } + + private static bool IsAttributeRoutedAction(ControllerActionDescriptor actionDescriptor) + { + return actionDescriptor.AttributeRouteInfo?.Template != null; + } + + private static IList AddErrorNumbers( + IEnumerable namedRoutedErrors) + { + return namedRoutedErrors + .Select((error, i) => + Resources.FormatAttributeRoute_AggregateErrorMessage_ErrorNumber( + i + 1, + Environment.NewLine, + error)) + .ToList(); + } + + private static IList ValidateNamedAttributeRoutedActions( + IDictionary> actionsGroupedByRouteName) + { + var namedRouteErrors = new List(); + + foreach (var kvp in actionsGroupedByRouteName) + { + // We are looking for attribute routed actions that have the same name but + // different route templates. We pick the first template of the group and + // we compare it against the rest of the templates that have that same name + // associated. + // The moment we find one that is different we report the whole group to the + // user in the error message so that he can see the different actions and the + // different templates for a given named attribute route. + var firstActionDescriptor = kvp.Value[0]; + var firstTemplate = firstActionDescriptor.AttributeRouteInfo.Template; + + for (var i = 1; i < kvp.Value.Count; i++) + { + var otherActionDescriptor = kvp.Value[i]; + var otherActionTemplate = otherActionDescriptor.AttributeRouteInfo.Template; + + if (!firstTemplate.Equals(otherActionTemplate, StringComparison.OrdinalIgnoreCase)) + { + var descriptions = kvp.Value.Select(ad => + Resources.FormatAttributeRoute_DuplicateNames_Item( + ad.DisplayName, + ad.AttributeRouteInfo.Template)); + + var errorDescription = string.Join(Environment.NewLine, descriptions); + var message = Resources.FormatAttributeRoute_DuplicateNames( + kvp.Key, + Environment.NewLine, + errorDescription); + + namedRouteErrors.Add(message); + break; + } + } + } + + return namedRouteErrors; + } + + private static void ValidateActionGroupConfiguration( + IDictionary>> methodMap, + ControllerActionDescriptor actionDescriptor, + IDictionary routingConfigurationErrors) + { + var hasAttributeRoutedActions = false; + var hasConventionallyRoutedActions = false; + + var actionsForMethod = methodMap[actionDescriptor.MethodInfo]; + foreach (var reflectedAction in actionsForMethod) + { + foreach (var action in reflectedAction.Value) + { + if (IsAttributeRoutedAction(action)) + { + hasAttributeRoutedActions = true; + } + else + { + hasConventionallyRoutedActions = true; + } + } + } + + // Validate that no method result in attribute and non attribute actions at the same time. + // By design, mixing attribute and conventionally actions in the same method is not allowed. + // + // This for example: + // + // [HttpGet] + // [HttpPost("Foo")] + // public void Foo() { } + if (hasAttributeRoutedActions && hasConventionallyRoutedActions) + { + var message = CreateMixedRoutedActionDescriptorsErrorMessage( + actionDescriptor, + actionsForMethod); + + routingConfigurationErrors.Add(actionDescriptor.MethodInfo, message); + } + } + + private static string CreateMixedRoutedActionDescriptorsErrorMessage( + ControllerActionDescriptor actionDescriptor, + IDictionary> actionsForMethod) + { + // Text to show as the attribute route template for conventionally routed actions. + var nullTemplate = Resources.AttributeRoute_NullTemplateRepresentation; + + var actionDescriptions = new List(); + foreach (var action in actionsForMethod.SelectMany(kvp => kvp.Value)) + { + var routeTemplate = action.AttributeRouteInfo?.Template ?? nullTemplate; + + var verbs = action.ActionConstraints?.OfType() + .FirstOrDefault()?.HttpMethods; + + var formattedVerbs = string.Empty; + if (verbs != null) + { + formattedVerbs = string.Join(", ", verbs.OrderBy(v => v, StringComparer.OrdinalIgnoreCase)); + } + + var description = + Resources.FormatAttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod_Item( + action.DisplayName, + routeTemplate, + formattedVerbs); + + actionDescriptions.Add(description); + } + + // Sample error message: + // + // A method 'MyApplication.CustomerController.Index' must not define attributed actions and + // non attributed actions at the same time: + // Action: 'MyApplication.CustomerController.Index' - Route Template: 'Products' - HTTP Verbs: 'PUT' + // Action: 'MyApplication.CustomerController.Index' - Route Template: '(none)' - HTTP Verbs: 'POST' + // + // Use 'AcceptVerbsAttribute' to create a single route that allows multiple HTTP verbs and defines a route, + // or set a route template in all attributes that constrain HTTP verbs. + return + Resources.FormatAttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod( + actionDescriptor.DisplayName, + Environment.NewLine, + string.Join(Environment.NewLine, actionDescriptions)); + } + + private static string CreateAttributeRoutingAggregateErrorMessage( + IEnumerable individualErrors) + { + var errorMessages = AddErrorNumbers(individualErrors); + + var message = Resources.FormatAttributeRoute_AggregateErrorMessage( + Environment.NewLine, + string.Join(Environment.NewLine + Environment.NewLine, errorMessages)); + return message; + } + + // We need to build a map of methods to reflected actions and reflected actions to + // action descriptors so that we can validate later that no method produced attribute + // and non attributed actions at the same time, and that no method that produced attribute + // routed actions has no attributes that implement IActionHttpMethodProvider and do not + // implement IRouteTemplateProvider. For example: + // + // public class ProductsController + // { + // [HttpGet("Products")] + // [HttpPost] + // public ActionResult Items(){ ... } + // + // [HttpGet("Products")] + // [CustomHttpMethods("POST, PUT")] + // public ActionResult List(){ ... } + // } + private class MethodToActionMap : + Dictionary>> + { + public void AddToMethodInfo( + ActionModel action, + IList actionDescriptors) + { + if (TryGetValue(action.ActionMethod, out var actionsForMethod)) + { + actionsForMethod.Add(action, actionDescriptors); + } + else + { + var reflectedActionMap = + new Dictionary>(); + reflectedActionMap.Add(action, actionDescriptors); + Add(action.ActionMethod, reflectedActionMap); + } + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorProvider.cs new file mode 100644 index 0000000000..c5fc31e178 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorProvider.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.Reflection; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ControllerActionDescriptorProvider : IActionDescriptorProvider + { + private readonly ApplicationPartManager _partManager; + private readonly IApplicationModelProvider[] _applicationModelProviders; + private readonly IEnumerable _conventions; + + public ControllerActionDescriptorProvider( + ApplicationPartManager partManager, + IEnumerable applicationModelProviders, + IOptions optionsAccessor) + { + if (partManager == null) + { + throw new ArgumentNullException(nameof(partManager)); + } + + if (applicationModelProviders == null) + { + throw new ArgumentNullException(nameof(applicationModelProviders)); + } + + if (optionsAccessor == null) + { + throw new ArgumentNullException(nameof(optionsAccessor)); + } + + _partManager = partManager; + _applicationModelProviders = applicationModelProviders.OrderBy(p => p.Order).ToArray(); + _conventions = optionsAccessor.Value.Conventions; + } + + public int Order => -1000; + + /// + public void OnProvidersExecuting(ActionDescriptorProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + foreach (var descriptor in GetDescriptors()) + { + context.Results.Add(descriptor); + } + } + + /// + public void OnProvidersExecuted(ActionDescriptorProviderContext context) + { + // After all of the providers have run, we need to provide a 'null' for each all of route values that + // participate in action selection. + // + // This is important for scenarios like Razor Pages, that use the 'page' route value. An action that + // uses 'page' shouldn't match when 'action' is set, and an action that uses 'action' shouldn't match when + // 'page is specified. + // + // Or for another example, consider areas. A controller that's not in an area needs a 'null' value for + // area so it can't match when the route produces an 'area' value. + var keys = new HashSet(StringComparer.OrdinalIgnoreCase); + for (var i = 0; i < context.Results.Count; i++) + { + var action = context.Results[i]; + foreach (var key in action.RouteValues.Keys) + { + keys.Add(key); + } + } + + for (var i = 0; i < context.Results.Count; i++) + { + var action = context.Results[i]; + foreach (var key in keys) + { + if (!action.RouteValues.ContainsKey(key)) + { + action.RouteValues.Add(key, null); + } + } + } + } + + protected internal IEnumerable GetDescriptors() + { + var applicationModel = BuildModel(); + ApplicationModelConventions.ApplyConventions(applicationModel, _conventions); + return ControllerActionDescriptorBuilder.Build(applicationModel); + } + + protected internal ApplicationModel BuildModel() + { + var controllerTypes = GetControllerTypes(); + var context = new ApplicationModelProviderContext(controllerTypes); + + for (var i = 0; i < _applicationModelProviders.Length; i++) + { + _applicationModelProviders[i].OnProvidersExecuting(context); + } + + for (var i = _applicationModelProviders.Length - 1; i >= 0; i--) + { + _applicationModelProviders[i].OnProvidersExecuted(context); + } + + return context.Result; + } + + private IEnumerable GetControllerTypes() + { + var feature = new ControllerFeature(); + _partManager.PopulateFeature(feature); + + return feature.Controllers; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionFilter.cs new file mode 100644 index 0000000000..93e4c4774a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionFilter.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; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A filter implementation which delegates to the controller for action filter interfaces. + /// + public class ControllerActionFilter : IAsyncActionFilter, IOrderedFilter + { + // Controller-filter methods run farthest from the action by default. + /// + public int Order { get; set; } = int.MinValue; + + /// + public Task OnActionExecutionAsync( + ActionExecutingContext context, + ActionExecutionDelegate next) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + + var controller = context.Controller; + if (controller == null) + { + throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(context.Controller), + nameof(ActionExecutingContext))); + } + + if (controller is IAsyncActionFilter asyncActionFilter) + { + return asyncActionFilter.OnActionExecutionAsync(context, next); + } + else if (controller is IActionFilter actionFilter) + { + return ExecuteActionFilter(context, next, actionFilter); + } + else + { + return next(); + } + } + + private static async Task ExecuteActionFilter( + ActionExecutingContext context, + ActionExecutionDelegate next, + IActionFilter actionFilter) + { + actionFilter.OnActionExecuting(context); + if (context.Result == null) + { + actionFilter.OnActionExecuted(await next()); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs new file mode 100644 index 0000000000..f020c86bf3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs @@ -0,0 +1,472 @@ +// Copyright (c) .NET 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.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; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ControllerActionInvoker : ResourceInvoker, IActionInvoker + { + private readonly ControllerActionInvokerCacheEntry _cacheEntry; + private readonly ControllerContext _controllerContext; + + private Dictionary _arguments; + + private ActionExecutingContext _actionExecutingContext; + private ActionExecutedContext _actionExecutedContext; + + internal ControllerActionInvoker( + ILogger logger, + DiagnosticSource diagnosticSource, + IActionResultTypeMapper mapper, + ControllerContext controllerContext, + ControllerActionInvokerCacheEntry cacheEntry, + IFilterMetadata[] filters) + : base(diagnosticSource, logger, mapper, controllerContext, filters, controllerContext.ValueProviderFactories) + { + if (cacheEntry == null) + { + throw new ArgumentNullException(nameof(cacheEntry)); + } + + _cacheEntry = cacheEntry; + _controllerContext = controllerContext; + } + + // Internal for testing + internal ControllerContext ControllerContext => _controllerContext; + + protected override void ReleaseResources() + { + if (_instance != null && _cacheEntry.ControllerReleaser != null) + { + _cacheEntry.ControllerReleaser(_controllerContext, _instance); + } + } + + private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) + { + switch (next) + { + case State.ActionBegin: + { + var controllerContext = _controllerContext; + + _cursor.Reset(); + + _instance = _cacheEntry.ControllerFactory(controllerContext); + + _arguments = new Dictionary(StringComparer.OrdinalIgnoreCase); + + var task = BindArgumentsAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ActionNext; + return task; + } + + goto case State.ActionNext; + } + + case State.ActionNext: + { + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) + { + if (_actionExecutingContext == null) + { + _actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _instance); + } + + state = current.FilterAsync; + goto case State.ActionAsyncBegin; + } + else if (current.Filter != null) + { + if (_actionExecutingContext == null) + { + _actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _instance); + } + + state = current.Filter; + goto case State.ActionSyncBegin; + } + else + { + goto case State.ActionInside; + } + } + + case State.ActionAsyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_actionExecutingContext != null); + + var filter = (IAsyncActionFilter)state; + var actionExecutingContext = _actionExecutingContext; + + _diagnosticSource.BeforeOnActionExecution(actionExecutingContext, filter); + _logger.BeforeExecutingMethodOnFilter( + MvcCoreLoggerExtensions.ActionFilter, + nameof(IAsyncActionFilter.OnActionExecutionAsync), + filter); + + var task = filter.OnActionExecutionAsync(actionExecutingContext, InvokeNextActionFilterAwaitedAsync); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ActionAsyncEnd; + return task; + } + + goto case State.ActionAsyncEnd; + } + + case State.ActionAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_actionExecutingContext != null); + + var filter = (IAsyncActionFilter)state; + + if (_actionExecutedContext == null) + { + // If we get here then the filter didn't call 'next' indicating a short circuit. + _logger.ActionFilterShortCircuited(filter); + + _actionExecutedContext = new ActionExecutedContext( + _controllerContext, + _filters, + _instance) + { + Canceled = true, + Result = _actionExecutingContext.Result, + }; + } + + _diagnosticSource.AfterOnActionExecution(_actionExecutedContext, filter); + _logger.AfterExecutingMethodOnFilter( + MvcCoreLoggerExtensions.ActionFilter, + nameof(IAsyncActionFilter.OnActionExecutionAsync), + filter); + + goto case State.ActionEnd; + } + + case State.ActionSyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_actionExecutingContext != null); + + var filter = (IActionFilter)state; + var actionExecutingContext = _actionExecutingContext; + + _diagnosticSource.BeforeOnActionExecuting(actionExecutingContext, filter); + _logger.BeforeExecutingMethodOnFilter( + MvcCoreLoggerExtensions.ActionFilter, + nameof(IActionFilter.OnActionExecuting), + filter); + + filter.OnActionExecuting(actionExecutingContext); + + _diagnosticSource.AfterOnActionExecuting(actionExecutingContext, filter); + _logger.AfterExecutingMethodOnFilter( + MvcCoreLoggerExtensions.ActionFilter, + nameof(IActionFilter.OnActionExecuting), + filter); + + if (actionExecutingContext.Result != null) + { + // Short-circuited by setting a result. + _logger.ActionFilterShortCircuited(filter); + + _actionExecutedContext = new ActionExecutedContext( + _actionExecutingContext, + _filters, + _instance) + { + Canceled = true, + Result = _actionExecutingContext.Result, + }; + + goto case State.ActionEnd; + } + + var task = InvokeNextActionFilterAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ActionSyncEnd; + return task; + } + + goto case State.ActionSyncEnd; + } + + case State.ActionSyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_actionExecutingContext != null); + Debug.Assert(_actionExecutedContext != null); + + var filter = (IActionFilter)state; + var actionExecutedContext = _actionExecutedContext; + + _diagnosticSource.BeforeOnActionExecuted(actionExecutedContext, filter); + _logger.BeforeExecutingMethodOnFilter( + MvcCoreLoggerExtensions.ActionFilter, + nameof(IActionFilter.OnActionExecuted), + filter); + + filter.OnActionExecuted(actionExecutedContext); + + _diagnosticSource.AfterOnActionExecuted(actionExecutedContext, filter); + _logger.AfterExecutingMethodOnFilter( + MvcCoreLoggerExtensions.ActionFilter, + nameof(IActionFilter.OnActionExecuted), + filter); + + goto case State.ActionEnd; + } + + case State.ActionInside: + { + var task = InvokeActionMethodAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ActionEnd; + return task; + } + + goto case State.ActionEnd; + } + + case State.ActionEnd: + { + if (scope == Scope.Action) + { + if (_actionExecutedContext == null) + { + _actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _instance) + { + Result = _result, + }; + } + + isCompleted = true; + return Task.CompletedTask; + } + + var actionExecutedContext = _actionExecutedContext; + Rethrow(actionExecutedContext); + + if (actionExecutedContext != null) + { + _result = actionExecutedContext.Result; + } + + isCompleted = true; + return Task.CompletedTask; + } + + default: + throw new InvalidOperationException(); + } + } + + private async Task InvokeNextActionFilterAsync() + { + try + { + var next = State.ActionNext; + var state = (object)null; + var scope = Scope.Action; + var isCompleted = false; + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); + } + } + catch (Exception exception) + { + _actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _instance) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), + }; + } + + Debug.Assert(_actionExecutedContext != null); + } + + private async Task InvokeNextActionFilterAwaitedAsync() + { + Debug.Assert(_actionExecutingContext != null); + if (_actionExecutingContext.Result != null) + { + // If we get here, it means that an async filter set a result AND called next(). This is forbidden. + var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit( + typeof(IAsyncActionFilter).Name, + nameof(ActionExecutingContext.Result), + typeof(ActionExecutingContext).Name, + typeof(ActionExecutionDelegate).Name); + + throw new InvalidOperationException(message); + } + + await InvokeNextActionFilterAsync(); + + Debug.Assert(_actionExecutedContext != null); + return _actionExecutedContext; + } + + private async Task InvokeActionMethodAsync() + { + var controllerContext = _controllerContext; + var objectMethodExecutor = _cacheEntry.ObjectMethodExecutor; + var controller = _instance; + var arguments = _arguments; + var actionMethodExecutor = _cacheEntry.ActionMethodExecutor; + var orderedArguments = PrepareArguments(arguments, objectMethodExecutor); + + var diagnosticSource = _diagnosticSource; + var logger = _logger; + + IActionResult result = null; + try + { + diagnosticSource.BeforeActionMethod( + controllerContext, + arguments, + controller); + logger.ActionMethodExecuting(controllerContext, orderedArguments); + var stopwatch = ValueStopwatch.StartNew(); + var actionResultValueTask = actionMethodExecutor.Execute(_mapper, objectMethodExecutor, controller, orderedArguments); + if (actionResultValueTask.IsCompletedSuccessfully) + { + result = actionResultValueTask.Result; + } + else + { + result = await actionResultValueTask; + } + + _result = result; + logger.ActionMethodExecuted(controllerContext, result, stopwatch.GetElapsedTime()); + } + finally + { + diagnosticSource.AfterActionMethod( + controllerContext, + arguments, + controllerContext, + result); + } + } + + /// for details on what the + /// variables in this method represent. + protected override async Task InvokeInnerFilterAsync() + { + var next = State.ActionBegin; + var scope = Scope.Invoker; + var state = (object)null; + var isCompleted = false; + + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); + } + } + + private static void Rethrow(ActionExecutedContext context) + { + if (context == null) + { + return; + } + + if (context.ExceptionHandled) + { + return; + } + + if (context.ExceptionDispatchInfo != null) + { + context.ExceptionDispatchInfo.Throw(); + } + + if (context.Exception != null) + { + throw context.Exception; + } + } + + private Task BindArgumentsAsync() + { + // Perf: Avoid allocating async state machines where possible. We only need the state + // machine if you need to bind properties or arguments. + var actionDescriptor = _controllerContext.ActionDescriptor; + if (actionDescriptor.BoundProperties.Count == 0 && + actionDescriptor.Parameters.Count == 0) + { + return Task.CompletedTask; + } + + Debug.Assert(_cacheEntry.ControllerBinderDelegate != null); + return _cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments); + } + + private static object[] PrepareArguments( + IDictionary actionParameters, + ObjectMethodExecutor actionMethodExecutor) + { + var declaredParameterInfos = actionMethodExecutor.MethodParameters; + var count = declaredParameterInfos.Length; + if (count == 0) + { + return null; + } + + var arguments = new object[count]; + for (var index = 0; index < count; index++) + { + var parameterInfo = declaredParameterInfos[index]; + + if (!actionParameters.TryGetValue(parameterInfo.Name, out var value)) + { + value = actionMethodExecutor.GetDefaultValueForParameter(index); + } + + arguments[index] = value; + } + + return arguments; + } + + private enum Scope + { + Invoker, + Action, + } + + private enum State + { + ActionBegin, + ActionNext, + ActionAsyncBegin, + ActionAsyncEnd, + ActionSyncBegin, + ActionSyncEnd, + ActionInside, + ActionEnd, + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCache.cs new file mode 100644 index 0000000000..0c6bd2adc1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCache.cs @@ -0,0 +1,124 @@ +// Copyright (c) .NET 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.Concurrent; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ControllerActionInvokerCache + { + private readonly IActionDescriptorCollectionProvider _collectionProvider; + private readonly ParameterBinder _parameterBinder; + private readonly IModelBinderFactory _modelBinderFactory; + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly IFilterProvider[] _filterProviders; + private readonly IControllerFactoryProvider _controllerFactoryProvider; + private readonly MvcOptions _mvcOptions; + private volatile InnerCache _currentCache; + + public ControllerActionInvokerCache( + IActionDescriptorCollectionProvider collectionProvider, + ParameterBinder parameterBinder, + IModelBinderFactory modelBinderFactory, + IModelMetadataProvider modelMetadataProvider, + IEnumerable filterProviders, + IControllerFactoryProvider factoryProvider, + IOptions mvcOptions) + { + _collectionProvider = collectionProvider; + _parameterBinder = parameterBinder; + _modelBinderFactory = modelBinderFactory; + _modelMetadataProvider = modelMetadataProvider; + _filterProviders = filterProviders.OrderBy(item => item.Order).ToArray(); + _controllerFactoryProvider = factoryProvider; + _mvcOptions = mvcOptions.Value; + } + + private InnerCache CurrentCache + { + get + { + var current = _currentCache; + var actionDescriptors = _collectionProvider.ActionDescriptors; + + if (current == null || current.Version != actionDescriptors.Version) + { + current = new InnerCache(actionDescriptors.Version); + _currentCache = current; + } + + return current; + } + } + + public (ControllerActionInvokerCacheEntry cacheEntry, IFilterMetadata[] filters) GetCachedResult(ControllerContext controllerContext) + { + var cache = CurrentCache; + var actionDescriptor = controllerContext.ActionDescriptor; + + IFilterMetadata[] filters; + if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry)) + { + var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, controllerContext); + filters = filterFactoryResult.Filters; + + var parameterDefaultValues = ParameterDefaultValues + .GetParameterDefaultValues(actionDescriptor.MethodInfo); + + var objectMethodExecutor = ObjectMethodExecutor.Create( + actionDescriptor.MethodInfo, + actionDescriptor.ControllerTypeInfo, + parameterDefaultValues); + + var controllerFactory = _controllerFactoryProvider.CreateControllerFactory(actionDescriptor); + var controllerReleaser = _controllerFactoryProvider.CreateControllerReleaser(actionDescriptor); + var propertyBinderFactory = ControllerBinderDelegateProvider.CreateBinderDelegate( + _parameterBinder, + _modelBinderFactory, + _modelMetadataProvider, + actionDescriptor, + _mvcOptions); + + var actionMethodExecutor = ActionMethodExecutor.GetExecutor(objectMethodExecutor); + + cacheEntry = new ControllerActionInvokerCacheEntry( + filterFactoryResult.CacheableFilters, + controllerFactory, + controllerReleaser, + propertyBinderFactory, + objectMethodExecutor, + actionMethodExecutor); + cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry); + } + else + { + // Filter instances from statically defined filter descriptors + from filter providers + filters = FilterFactory.CreateUncachedFilters(_filterProviders, controllerContext, cacheEntry.CachedFilters); + } + + return (cacheEntry, filters); + } + + private class InnerCache + { + public InnerCache(int version) + { + Version = version; + } + + public ConcurrentDictionary Entries { get; } = + new ConcurrentDictionary(); + + public int Version { get; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCacheEntry.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCacheEntry.cs new file mode 100644 index 0000000000..6b06f0e02b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCacheEntry.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 Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ControllerActionInvokerCacheEntry + { + internal ControllerActionInvokerCacheEntry( + FilterItem[] cachedFilters, + Func controllerFactory, + Action controllerReleaser, + ControllerBinderDelegate controllerBinderDelegate, + ObjectMethodExecutor objectMethodExecutor, + ActionMethodExecutor actionMethodExecutor) + { + ControllerFactory = controllerFactory; + ControllerReleaser = controllerReleaser; + ControllerBinderDelegate = controllerBinderDelegate; + CachedFilters = cachedFilters; + ObjectMethodExecutor = objectMethodExecutor; + ActionMethodExecutor = actionMethodExecutor; + } + + public FilterItem[] CachedFilters { get; } + + public Func ControllerFactory { get; } + + public Action ControllerReleaser { get; } + + public ControllerBinderDelegate ControllerBinderDelegate { get; } + + internal ObjectMethodExecutor ObjectMethodExecutor { get; } + + internal ActionMethodExecutor ActionMethodExecutor { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerProvider.cs new file mode 100644 index 0000000000..b9b135ff52 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerProvider.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 System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ControllerActionInvokerProvider : IActionInvokerProvider + { + private readonly ControllerActionInvokerCache _controllerActionInvokerCache; + private readonly IReadOnlyList _valueProviderFactories; + private readonly int _maxModelValidationErrors; + private readonly ILogger _logger; + private readonly DiagnosticSource _diagnosticSource; + private readonly IActionResultTypeMapper _mapper; + + public ControllerActionInvokerProvider( + ControllerActionInvokerCache controllerActionInvokerCache, + IOptions optionsAccessor, + ILoggerFactory loggerFactory, + DiagnosticSource diagnosticSource, + IActionResultTypeMapper mapper) + { + _controllerActionInvokerCache = controllerActionInvokerCache; + _valueProviderFactories = optionsAccessor.Value.ValueProviderFactories.ToArray(); + _maxModelValidationErrors = optionsAccessor.Value.MaxModelValidationErrors; + _logger = loggerFactory.CreateLogger(); + _diagnosticSource = diagnosticSource; + _mapper = mapper; + } + + public int Order => -1000; + + /// + public void OnProvidersExecuting(ActionInvokerProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.ActionContext.ActionDescriptor is ControllerActionDescriptor) + { + var controllerContext = new ControllerContext(context.ActionContext); + // PERF: These are rarely going to be changed, so let's go copy-on-write. + controllerContext.ValueProviderFactories = new CopyOnWriteList(_valueProviderFactories); + controllerContext.ModelState.MaxAllowedErrors = _maxModelValidationErrors; + + var cacheResult = _controllerActionInvokerCache.GetCachedResult(controllerContext); + + var invoker = new ControllerActionInvoker( + _logger, + _diagnosticSource, + _mapper, + controllerContext, + cacheResult.cacheEntry, + cacheResult.filters); + + context.Result = invoker; + } + } + + /// + public void OnProvidersExecuted(ActionInvokerProviderContext context) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegate.cs new file mode 100644 index 0000000000..6a986160f0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegate.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 System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public delegate Task ControllerBinderDelegate( + ControllerContext controllerContext, + object controller, + Dictionary arguments); +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs new file mode 100644 index 0000000000..e29525ea11 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs @@ -0,0 +1,210 @@ +// Copyright (c) .NET 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.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + // Note: changes made to binding behavior in type should also be made to PageBinderFactory. + public static class ControllerBinderDelegateProvider + { + public static ControllerBinderDelegate CreateBinderDelegate( + ParameterBinder parameterBinder, + IModelBinderFactory modelBinderFactory, + IModelMetadataProvider modelMetadataProvider, + ControllerActionDescriptor actionDescriptor, + MvcOptions mvcOptions) + { + if (parameterBinder == null) + { + throw new ArgumentNullException(nameof(parameterBinder)); + } + + if (modelBinderFactory == null) + { + throw new ArgumentNullException(nameof(modelBinderFactory)); + } + + if (modelMetadataProvider == null) + { + throw new ArgumentNullException(nameof(modelMetadataProvider)); + } + + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + if (mvcOptions == null) + { + throw new ArgumentNullException(nameof(mvcOptions)); + } + + var parameterBindingInfo = GetParameterBindingInfo( + modelBinderFactory, + modelMetadataProvider, + actionDescriptor, + mvcOptions); + var propertyBindingInfo = GetPropertyBindingInfo(modelBinderFactory, modelMetadataProvider, actionDescriptor); + + if (parameterBindingInfo == null && propertyBindingInfo == null) + { + return null; + } + + return Bind; + + async Task Bind(ControllerContext controllerContext, object controller, Dictionary arguments) + { + var valueProvider = await CompositeValueProvider.CreateAsync(controllerContext); + var parameters = actionDescriptor.Parameters; + + for (var i = 0; i < parameters.Count; i++) + { + var parameter = parameters[i]; + var bindingInfo = parameterBindingInfo[i]; + var modelMetadata = bindingInfo.ModelMetadata; + + if (!modelMetadata.IsBindingAllowed) + { + continue; + } + + var result = await parameterBinder.BindModelAsync( + controllerContext, + bindingInfo.ModelBinder, + valueProvider, + parameter, + modelMetadata, + value: null); + + if (result.IsModelSet) + { + arguments[parameter.Name] = result.Model; + } + } + + var properties = actionDescriptor.BoundProperties; + for (var i = 0; i < properties.Count; i++) + { + var property = properties[i]; + var bindingInfo = propertyBindingInfo[i]; + var modelMetadata = bindingInfo.ModelMetadata; + + if (!modelMetadata.IsBindingAllowed) + { + continue; + } + + var result = await parameterBinder.BindModelAsync( + controllerContext, + bindingInfo.ModelBinder, + valueProvider, + property, + modelMetadata, + value: null); + + if (result.IsModelSet) + { + PropertyValueSetter.SetValue(bindingInfo.ModelMetadata, controller, result.Model); + } + } + } + } + + private static BinderItem[] GetParameterBindingInfo( + IModelBinderFactory modelBinderFactory, + IModelMetadataProvider modelMetadataProvider, + ControllerActionDescriptor actionDescriptor, + MvcOptions mvcOptions) + { + var parameters = actionDescriptor.Parameters; + if (parameters.Count == 0) + { + return null; + } + + var parameterBindingInfo = new BinderItem[parameters.Count]; + for (var i = 0; i < parameters.Count; i++) + { + var parameter = parameters[i]; + + ModelMetadata metadata; + if (mvcOptions.AllowValidatingTopLevelNodes && + modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase && + parameter is ControllerParameterDescriptor controllerParameterDescriptor) + { + // The default model metadata provider derives from ModelMetadataProvider + // and can therefore supply information about attributes applied to parameters. + metadata = modelMetadataProviderBase.GetMetadataForParameter(controllerParameterDescriptor.ParameterInfo); + } + else + { + // For backward compatibility, if there's a custom model metadata provider that + // only implements the older IModelMetadataProvider interface, access the more + // limited metadata information it supplies. In this scenario, validation attributes + // are not supported on parameters. + metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType); + } + + var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext + { + BindingInfo = parameter.BindingInfo, + Metadata = metadata, + CacheToken = parameter, + }); + + parameterBindingInfo[i] = new BinderItem(binder, metadata); + } + + return parameterBindingInfo; + } + + private static BinderItem[] GetPropertyBindingInfo( + IModelBinderFactory modelBinderFactory, + IModelMetadataProvider modelMetadataProvider, + ControllerActionDescriptor actionDescriptor) + { + var properties = actionDescriptor.BoundProperties; + if (properties.Count == 0) + { + return null; + } + + var propertyBindingInfo = new BinderItem[properties.Count]; + var controllerType = actionDescriptor.ControllerTypeInfo.AsType(); + for (var i = 0; i < properties.Count; i++) + { + var property = properties[i]; + var metadata = modelMetadataProvider.GetMetadataForProperty(controllerType, property.Name); + var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext + { + BindingInfo = property.BindingInfo, + Metadata = metadata, + CacheToken = property, + }); + + propertyBindingInfo[i] = new BinderItem(binder, metadata); + } + + return propertyBindingInfo; + } + + private struct BinderItem + { + public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata) + { + ModelBinder = modelBinder; + ModelMetadata = modelMetadata; + } + + public IModelBinder ModelBinder { get; } + + public ModelMetadata ModelMetadata { get; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerResultFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerResultFilter.cs new file mode 100644 index 0000000000..3e30debccc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerResultFilter.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; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A filter implementation which delegates to the controller for result filter interfaces. + /// + public class ControllerResultFilter : IAsyncResultFilter, IOrderedFilter + { + // Controller-filter methods run farthest from the result by default. + /// + public int Order { get; set; } = int.MinValue; + + /// + public Task OnResultExecutionAsync( + ResultExecutingContext context, + ResultExecutionDelegate next) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + + var controller = context.Controller; + if (controller == null) + { + throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(context.Controller), + nameof(ResultExecutingContext))); + } + + if (controller is IAsyncResultFilter asyncResultFilter) + { + return asyncResultFilter.OnResultExecutionAsync(context, next); + } + else if (controller is IResultFilter resultFilter) + { + return ExecuteResultFilter(context, next, resultFilter); + } + else + { + return next(); + } + } + + private static async Task ExecuteResultFilter( + ResultExecutingContext context, + ResultExecutionDelegate next, + IResultFilter resultFilter) + { + resultFilter.OnResultExecuting(context); + if (!context.Cancel) + { + resultFilter.OnResultExecuted(await next()); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/CopyOnWriteList.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/CopyOnWriteList.cs new file mode 100644 index 0000000000..a051b69cff --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/CopyOnWriteList.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.Collections; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class CopyOnWriteList : IList + { + private readonly IReadOnlyList _source; + private List _copy; + + public CopyOnWriteList(IReadOnlyList source) + { + _source = source; + } + + protected IReadOnlyList Readable => _copy ?? _source; + + protected List Writable + { + get + { + if (_copy == null) + { + _copy = new List(_source); + } + + return _copy; + } + } + + public T this[int index] + { + get => Readable[index]; + set => Writable[index] = value; + } + + public int Count => Readable.Count; + + public bool IsReadOnly => false; + + public void Add(T item) + { + Writable.Add(item); + } + + public void Clear() + { + Writable.Clear(); + } + + public bool Contains(T item) + { + foreach (var obj in Readable) + { + if (object.Equals(obj, item)) + { + return true; + } + } + + return false; + } + + public void CopyTo(T[] array, int arrayIndex) + { + foreach (var item in Readable) + { + array[arrayIndex++] = item; + } + } + + public IEnumerator GetEnumerator() + { + return Readable.GetEnumerator(); + } + + public int IndexOf(T item) + { + var i = 0; + foreach (var obj in Readable) + { + if (object.Equals(obj, item)) + { + return i; + } + + i++; + } + + return -1; + } + + public void Insert(int index, T item) + { + Writable.Insert(index, item); + } + + public bool Remove(T item) + { + return Writable.Remove(item); + } + + public void RemoveAt(int index) + { + Writable.RemoveAt(index); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultActionConstraintProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultActionConstraintProvider.cs new file mode 100644 index 0000000000..571f3ebebf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultActionConstraintProvider.cs @@ -0,0 +1,64 @@ +// Copyright (c) .NET 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.ActionConstraints; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A default implementation of . + /// + /// + /// This provider is able to provide an instance when the + /// implements or + /// / + /// + public class DefaultActionConstraintProvider : IActionConstraintProvider + { + /// + public int Order => -1000; + + /// + public void OnProvidersExecuting(ActionConstraintProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + for (var i = 0; i < context.Results.Count; i++) + { + ProvideConstraint(context.Results[i], context.HttpContext.RequestServices); + } + } + + /// + public void OnProvidersExecuted(ActionConstraintProviderContext context) + { + } + + private void ProvideConstraint(ActionConstraintItem item, IServiceProvider services) + { + // Don't overwrite anything that was done by a previous provider. + if (item.Constraint != null) + { + return; + } + + if (item.Metadata is IActionConstraint constraint) + { + item.Constraint = constraint; + item.IsReusable = true; + return; + } + + if (item.Metadata is IActionConstraintFactory factory) + { + item.Constraint = factory.CreateInstance(services); + item.IsReusable = factory.IsReusable; + return; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs new file mode 100644 index 0000000000..fc380933c2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs @@ -0,0 +1,692 @@ +// Copyright (c) .NET 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.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class DefaultApplicationModelProvider : IApplicationModelProvider + { + private readonly MvcOptions _mvcOptions; + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly Func _supportsAllRequests; + private readonly Func _supportsNonGetRequests; + + public DefaultApplicationModelProvider( + IOptions mvcOptionsAccessor, + IModelMetadataProvider modelMetadataProvider) + { + _mvcOptions = mvcOptionsAccessor.Value; + _modelMetadataProvider = modelMetadataProvider; + + _supportsAllRequests = _ => true; + _supportsNonGetRequests = context => !string.Equals(context.HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase); + } + + /// + public int Order => -1000; + + /// + public virtual void OnProvidersExecuting(ApplicationModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + foreach (var filter in _mvcOptions.Filters) + { + context.Result.Filters.Add(filter); + } + + foreach (var controllerType in context.ControllerTypes) + { + var controllerModel = CreateControllerModel(controllerType); + if (controllerModel == null) + { + continue; + } + + context.Result.Controllers.Add(controllerModel); + controllerModel.Application = context.Result; + + foreach (var propertyHelper in PropertyHelper.GetProperties(controllerType.AsType())) + { + var propertyInfo = propertyHelper.Property; + var propertyModel = CreatePropertyModel(propertyInfo); + if (propertyModel != null) + { + propertyModel.Controller = controllerModel; + controllerModel.ControllerProperties.Add(propertyModel); + } + } + + foreach (var methodInfo in controllerType.AsType().GetMethods()) + { + var actionModel = CreateActionModel(controllerType, methodInfo); + if (actionModel == null) + { + continue; + } + + actionModel.Controller = controllerModel; + controllerModel.Actions.Add(actionModel); + + foreach (var parameterInfo in actionModel.ActionMethod.GetParameters()) + { + var parameterModel = CreateParameterModel(parameterInfo); + if (parameterModel != null) + { + parameterModel.Action = actionModel; + actionModel.Parameters.Add(parameterModel); + } + } + } + } + } + + /// + public virtual void OnProvidersExecuted(ApplicationModelProviderContext context) + { + // Intentionally empty. + } + + /// + /// Creates a for the given . + /// + /// The . + /// A for the given . + protected virtual ControllerModel CreateControllerModel(TypeInfo typeInfo) + { + if (typeInfo == null) + { + throw new ArgumentNullException(nameof(typeInfo)); + } + + // For attribute routes on a controller, we want to support 'overriding' routes on a derived + // class. So we need to walk up the hierarchy looking for the first class to define routes. + // + // Then we want to 'filter' the set of attributes, so that only the effective routes apply. + var currentTypeInfo = typeInfo; + var objectTypeInfo = typeof(object).GetTypeInfo(); + + IRouteTemplateProvider[] routeAttributes; + + do + { + routeAttributes = currentTypeInfo + .GetCustomAttributes(inherit: false) + .OfType() + .ToArray(); + + if (routeAttributes.Length > 0) + { + // Found 1 or more route attributes. + break; + } + + currentTypeInfo = currentTypeInfo.BaseType.GetTypeInfo(); + } + while (currentTypeInfo != objectTypeInfo); + + // CoreCLR returns IEnumerable from GetCustomAttributes - the OfType + // is needed to so that the result of ToArray() is object + var attributes = typeInfo.GetCustomAttributes(inherit: true); + + // This is fairly complicated so that we maintain referential equality between items in + // ControllerModel.Attributes and ControllerModel.Attributes[*].Attribute. + var filteredAttributes = new List(); + foreach (var attribute in attributes) + { + if (attribute is IRouteTemplateProvider) + { + // This attribute is a route-attribute, leave it out. + } + else + { + filteredAttributes.Add(attribute); + } + } + + filteredAttributes.AddRange(routeAttributes); + + attributes = filteredAttributes.ToArray(); + + var controllerModel = new ControllerModel(typeInfo, attributes); + + AddRange(controllerModel.Selectors, CreateSelectors(attributes)); + + controllerModel.ControllerName = + typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ? + typeInfo.Name.Substring(0, typeInfo.Name.Length - "Controller".Length) : + typeInfo.Name; + + AddRange(controllerModel.Filters, attributes.OfType()); + + foreach (var routeValueProvider in attributes.OfType()) + { + controllerModel.RouteValues.Add(routeValueProvider.RouteKey, routeValueProvider.RouteValue); + } + + var apiVisibility = attributes.OfType().FirstOrDefault(); + if (apiVisibility != null) + { + controllerModel.ApiExplorer.IsVisible = !apiVisibility.IgnoreApi; + } + + var apiGroupName = attributes.OfType().FirstOrDefault(); + if (apiGroupName != null) + { + controllerModel.ApiExplorer.GroupName = apiGroupName.GroupName; + } + + // Controllers can implement action filter and result filter interfaces. We add + // a special delegating filter implementation to the pipeline to handle it. + // + // This is needed because filters are instantiated before the controller. + if (typeof(IAsyncActionFilter).GetTypeInfo().IsAssignableFrom(typeInfo) || + typeof(IActionFilter).GetTypeInfo().IsAssignableFrom(typeInfo)) + { + controllerModel.Filters.Add(new ControllerActionFilter()); + } + if (typeof(IAsyncResultFilter).GetTypeInfo().IsAssignableFrom(typeInfo) || + typeof(IResultFilter).GetTypeInfo().IsAssignableFrom(typeInfo)) + { + controllerModel.Filters.Add(new ControllerResultFilter()); + } + + return controllerModel; + } + + /// + /// Creates a for the given . + /// + /// The . + /// A for the given . + protected virtual PropertyModel CreatePropertyModel(PropertyInfo propertyInfo) + { + if (propertyInfo == null) + { + throw new ArgumentNullException(nameof(propertyInfo)); + } + + var attributes = propertyInfo.GetCustomAttributes(inherit: true); + + // BindingInfo for properties can be either specified by decorating the property with binding specific attributes. + // ModelMetadata also adds information from the property's type and any configured IBindingMetadataProvider. + var modelMetadata = _modelMetadataProvider.GetMetadataForProperty(propertyInfo.DeclaringType, propertyInfo.Name); + var bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); + + if (bindingInfo == null) + { + // Look for BindPropertiesAttribute on the handler type if no BindingInfo was inferred for the property. + // This allows a user to enable model binding on properties by decorating the controller type with BindPropertiesAttribute. + var declaringType = propertyInfo.DeclaringType; + var bindPropertiesAttribute = declaringType.GetCustomAttribute(inherit: true); + if (bindPropertiesAttribute != null) + { + var requestPredicate = bindPropertiesAttribute.SupportsGet ? _supportsAllRequests : _supportsNonGetRequests; + bindingInfo = new BindingInfo + { + RequestPredicate = requestPredicate, + }; + } + } + + var propertyModel = new PropertyModel(propertyInfo, attributes) + { + PropertyName = propertyInfo.Name, + BindingInfo = bindingInfo, + }; + + return propertyModel; + } + + /// + /// Creates the instance for the given action . + /// + /// The controller . + /// The action . + /// + /// An instance for the given action or + /// null if the does not represent an action. + /// + protected virtual ActionModel CreateActionModel( + TypeInfo typeInfo, + MethodInfo methodInfo) + { + if (typeInfo == null) + { + throw new ArgumentNullException(nameof(typeInfo)); + } + + if (methodInfo == null) + { + throw new ArgumentNullException(nameof(methodInfo)); + } + + if (!IsAction(typeInfo, methodInfo)) + { + return null; + } + + // CoreCLR returns IEnumerable from GetCustomAttributes - the OfType + // is needed to so that the result of ToArray() is object + var attributes = methodInfo.GetCustomAttributes(inherit: true); + + var actionModel = new ActionModel(methodInfo, attributes); + + AddRange(actionModel.Filters, attributes.OfType()); + + var actionName = attributes.OfType().FirstOrDefault(); + if (actionName?.Name != null) + { + actionModel.ActionName = actionName.Name; + } + else + { + actionModel.ActionName = methodInfo.Name; + } + + var apiVisibility = attributes.OfType().FirstOrDefault(); + if (apiVisibility != null) + { + actionModel.ApiExplorer.IsVisible = !apiVisibility.IgnoreApi; + } + + var apiGroupName = attributes.OfType().FirstOrDefault(); + if (apiGroupName != null) + { + actionModel.ApiExplorer.GroupName = apiGroupName.GroupName; + } + + foreach (var routeValueProvider in attributes.OfType()) + { + actionModel.RouteValues.Add(routeValueProvider.RouteKey, routeValueProvider.RouteValue); + } + + // Now we need to determine the action selection info (cross-section of routes and constraints) + // + // For attribute routes on a action, we want to support 'overriding' routes on a + // virtual method, but allow 'overriding'. So we need to walk up the hierarchy looking + // for the first definition to define routes. + // + // Then we want to 'filter' the set of attributes, so that only the effective routes apply. + var currentMethodInfo = methodInfo; + + IRouteTemplateProvider[] routeAttributes; + + while (true) + { + routeAttributes = currentMethodInfo + .GetCustomAttributes(inherit: false) + .OfType() + .ToArray(); + + if (routeAttributes.Length > 0) + { + // Found 1 or more route attributes. + break; + } + + // GetBaseDefinition returns 'this' when it gets to the bottom of the chain. + var nextMethodInfo = currentMethodInfo.GetBaseDefinition(); + if (currentMethodInfo == nextMethodInfo) + { + break; + } + + currentMethodInfo = nextMethodInfo; + } + + // This is fairly complicated so that we maintain referential equality between items in + // ActionModel.Attributes and ActionModel.Attributes[*].Attribute. + var applicableAttributes = new List(); + foreach (var attribute in attributes) + { + if (attribute is IRouteTemplateProvider) + { + // This attribute is a route-attribute, leave it out. + } + else + { + applicableAttributes.Add(attribute); + } + } + + applicableAttributes.AddRange(routeAttributes); + AddRange(actionModel.Selectors, CreateSelectors(applicableAttributes)); + + return actionModel; + } + + /// + /// Returns true if the is an action. Otherwise false. + /// + /// The . + /// The . + /// true if the is an action. Otherwise false. + /// + /// Override this method to provide custom logic to determine which methods are considered actions. + /// + protected virtual bool IsAction(TypeInfo typeInfo, MethodInfo methodInfo) + { + if (typeInfo == null) + { + throw new ArgumentNullException(nameof(typeInfo)); + } + + if (methodInfo == null) + { + throw new ArgumentNullException(nameof(methodInfo)); + } + + // The SpecialName bit is set to flag members that are treated in a special way by some compilers + // (such as property accessors and operator overloading methods). + if (methodInfo.IsSpecialName) + { + return false; + } + + if (methodInfo.IsDefined(typeof(NonActionAttribute))) + { + return false; + } + + // Overridden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid. + if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object)) + { + return false; + } + + // Dispose method implemented from IDisposable is not valid + if (IsIDisposableMethod(methodInfo)) + { + return false; + } + + if (methodInfo.IsStatic) + { + return false; + } + + if (methodInfo.IsAbstract) + { + return false; + } + + if (methodInfo.IsConstructor) + { + return false; + } + + if (methodInfo.IsGenericMethod) + { + return false; + } + + return methodInfo.IsPublic; + } + + /// + /// Creates a for the given . + /// + /// The . + /// A for the given . + protected virtual ParameterModel CreateParameterModel(ParameterInfo parameterInfo) + { + if (parameterInfo == null) + { + throw new ArgumentNullException(nameof(parameterInfo)); + } + + var attributes = parameterInfo.GetCustomAttributes(inherit: true); + + BindingInfo bindingInfo; + if (_mvcOptions.AllowValidatingTopLevelNodes && _modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase) + { + var modelMetadata = modelMetadataProviderBase.GetMetadataForParameter(parameterInfo); + bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); + } + else + { + // GetMetadataForParameter should only be used if the user has opted in to the 2.1 behavior. + bindingInfo = BindingInfo.GetBindingInfo(attributes); + } + + var parameterModel = new ParameterModel(parameterInfo, attributes) + { + ParameterName = parameterInfo.Name, + BindingInfo = bindingInfo, + }; + + return parameterModel; + } + + private IList CreateSelectors(IList attributes) + { + // Route attributes create multiple selector models, we want to split the set of + // attributes based on these so each selector only has the attributes that affect it. + // + // The set of route attributes are split into those that 'define' a route versus those that are + // 'silent'. + // + // We need to define a selector for each attribute that 'defines' a route, and a single selector + // for all of the ones that don't (if any exist). + // + // If the attribute that 'defines' a route is NOT an IActionHttpMethodProvider, then we'll include with + // it, any IActionHttpMethodProvider that are 'silent' IRouteTemplateProviders. In this case the 'extra' + // action for silent route providers isn't needed. + // + // Ex: + // [HttpGet] + // [AcceptVerbs("POST", "PUT")] + // [HttpPost("Api/Things")] + // public void DoThing() + // + // This will generate 2 selectors: + // 1. [HttpPost("Api/Things")] + // 2. [HttpGet], [AcceptVerbs("POST", "PUT")] + // + // Another example of this situation is: + // + // [Route("api/Products")] + // [AcceptVerbs("GET", "HEAD")] + // [HttpPost("api/Products/new")] + // + // This will generate 2 selectors: + // 1. [AcceptVerbs("GET", "HEAD")] + // 2. [HttpPost] + // + // Note that having a route attribute that doesn't define a route template _might_ be an error. We + // don't have enough context to really know at this point so we just pass it on. + var routeProviders = new List(); + + var createSelectorForSilentRouteProviders = false; + foreach (var attribute in attributes) + { + if (attribute is IRouteTemplateProvider routeTemplateProvider) + { + if (IsSilentRouteAttribute(routeTemplateProvider)) + { + createSelectorForSilentRouteProviders = true; + } + else + { + routeProviders.Add(routeTemplateProvider); + } + } + } + + foreach (var routeProvider in routeProviders) + { + // If we see an attribute like + // [Route(...)] + // + // Then we want to group any attributes like [HttpGet] with it. + // + // Basically... + // + // [HttpGet] + // [HttpPost("Products")] + // public void Foo() { } + // + // Is two selectors. And... + // + // [HttpGet] + // [Route("Products")] + // public void Foo() { } + // + // Is one selector. + if (!(routeProvider is IActionHttpMethodProvider)) + { + createSelectorForSilentRouteProviders = false; + } + } + + var selectorModels = new List(); + if (routeProviders.Count == 0 && !createSelectorForSilentRouteProviders) + { + // Simple case, all attributes apply + selectorModels.Add(CreateSelectorModel(route: null, attributes: attributes)); + } + else + { + // Each of these routeProviders are the ones that actually have routing information on them + // something like [HttpGet] won't show up here, but [HttpGet("Products")] will. + foreach (var routeProvider in routeProviders) + { + var filteredAttributes = new List(); + foreach (var attribute in attributes) + { + if (ReferenceEquals(attribute, routeProvider)) + { + filteredAttributes.Add(attribute); + } + else if (InRouteProviders(routeProviders, attribute)) + { + // Exclude other route template providers + // Example: + // [HttpGet("template")] + // [Route("template/{id}")] + } + else if ( + routeProvider is IActionHttpMethodProvider && + attribute is IActionHttpMethodProvider) + { + // Example: + // [HttpGet("template")] + // [AcceptVerbs("GET", "POST")] + // + // Exclude other http method providers if this route is an + // http method provider. + } + else + { + filteredAttributes.Add(attribute); + } + } + + selectorModels.Add(CreateSelectorModel(routeProvider, filteredAttributes)); + } + + if (createSelectorForSilentRouteProviders) + { + var filteredAttributes = new List(); + foreach (var attribute in attributes) + { + if (!InRouteProviders(routeProviders, attribute)) + { + filteredAttributes.Add(attribute); + } + } + + selectorModels.Add(CreateSelectorModel(route: null, attributes: filteredAttributes)); + } + } + + return selectorModels; + } + + private static bool InRouteProviders(List routeProviders, object attribute) + { + foreach (var rp in routeProviders) + { + if (ReferenceEquals(rp, attribute)) + { + return true; + } + } + + return false; + } + + private static SelectorModel CreateSelectorModel(IRouteTemplateProvider route, IList attributes) + { + var selectorModel = new SelectorModel(); + if (route != null) + { + selectorModel.AttributeRouteModel = new AttributeRouteModel(route); + } + + AddRange(selectorModel.ActionConstraints, attributes.OfType()); + + // Simple case, all HTTP method attributes apply + var httpMethods = attributes + .OfType() + .SelectMany(a => a.HttpMethods) + .Distinct(StringComparer.OrdinalIgnoreCase) + .ToArray(); + + if (httpMethods.Length > 0) + { + selectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(httpMethods)); + } + + return selectorModel; + } + + private bool IsIDisposableMethod(MethodInfo methodInfo) + { + // Ideally we do not want Dispose method to be exposed as an action. However there are some scenarios where a user + // might want to expose a method with name "Dispose" (even though they might not be really disposing resources) + // Example: A controller deriving from MVC's Controller type might wish to have a method with name Dispose, + // in which case they can use the "new" keyword to hide the base controller's declaration. + + // Find where the method was originally declared + var baseMethodInfo = methodInfo.GetBaseDefinition(); + var declaringTypeInfo = baseMethodInfo.DeclaringType.GetTypeInfo(); + + return + (typeof(IDisposable).GetTypeInfo().IsAssignableFrom(declaringTypeInfo) && + declaringTypeInfo.GetRuntimeInterfaceMap(typeof(IDisposable)).TargetMethods[0] == baseMethodInfo); + } + + private bool IsSilentRouteAttribute(IRouteTemplateProvider routeTemplateProvider) + { + return + routeTemplateProvider.Template == null && + routeTemplateProvider.Order == null && + routeTemplateProvider.Name == null; + } + + private static void AddRange(IList list, IEnumerable items) + { + foreach (var item in items) + { + list.Add(item); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs new file mode 100644 index 0000000000..8e0546b992 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.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.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A default implementation of . + /// + public class DefaultBindingMetadataProvider : IBindingMetadataProvider + { + public void CreateBindingMetadata(BindingMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // BinderModelName + foreach (var binderModelNameAttribute in context.Attributes.OfType()) + { + if (binderModelNameAttribute?.Name != null) + { + context.BindingMetadata.BinderModelName = binderModelNameAttribute.Name; + break; + } + } + + // BinderType + foreach (var binderTypeAttribute in context.Attributes.OfType()) + { + if (binderTypeAttribute.BinderType != null) + { + context.BindingMetadata.BinderType = binderTypeAttribute.BinderType; + break; + } + } + + // BindingSource + foreach (var bindingSourceAttribute in context.Attributes.OfType()) + { + if (bindingSourceAttribute.BindingSource != null) + { + context.BindingMetadata.BindingSource = bindingSourceAttribute.BindingSource; + break; + } + } + + // PropertyFilterProvider + var propertyFilterProviders = context.Attributes.OfType().ToArray(); + if (propertyFilterProviders.Length == 0) + { + context.BindingMetadata.PropertyFilterProvider = null; + } + else if (propertyFilterProviders.Length == 1) + { + context.BindingMetadata.PropertyFilterProvider = propertyFilterProviders[0]; + } + else + { + var composite = new CompositePropertyFilterProvider(propertyFilterProviders); + context.BindingMetadata.PropertyFilterProvider = composite; + } + + var bindingBehavior = FindBindingBehavior(context); + if (bindingBehavior != null) + { + context.BindingMetadata.IsBindingAllowed = bindingBehavior.Behavior != BindingBehavior.Never; + context.BindingMetadata.IsBindingRequired = bindingBehavior.Behavior == BindingBehavior.Required; + } + } + + private static BindingBehaviorAttribute FindBindingBehavior(BindingMetadataProviderContext context) + { + switch (context.Key.MetadataKind) + { + case ModelMetadataKind.Property: + // BindingBehavior can fall back to attributes on the Container Type, but we should ignore + // attributes on the Property Type. + var matchingAttributes = context.PropertyAttributes.OfType(); + return matchingAttributes.FirstOrDefault() + ?? context.Key.ContainerType.GetTypeInfo() + .GetCustomAttributes(typeof(BindingBehaviorAttribute), inherit: true) + .OfType() + .FirstOrDefault(); + case ModelMetadataKind.Parameter: + return context.ParameterAttributes.OfType().FirstOrDefault(); + default: + return null; + } + } + + private class CompositePropertyFilterProvider : IPropertyFilterProvider + { + private readonly IEnumerable _providers; + + public CompositePropertyFilterProvider(IEnumerable providers) + { + _providers = providers; + } + + public Func PropertyFilter => CreatePropertyFilter(); + + private Func CreatePropertyFilter() + { + var propertyFilters = _providers + .Select(p => p.PropertyFilter) + .Where(p => p != null); + + return (m) => + { + foreach (var propertyFilter in propertyFilters) + { + if (!propertyFilter(m)) + { + return false; + } + } + + return true; + }; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCollectionValidationStrategy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCollectionValidationStrategy.cs new file mode 100644 index 0000000000..322f55d55b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCollectionValidationStrategy.cs @@ -0,0 +1,141 @@ +// Copyright (c) .NET 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.Concurrent; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using System.Linq.Expressions; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// The default implementation of for a collection. + /// + /// + /// This implementation handles cases like: + /// + /// Model: IList<Student> + /// Query String: ?students[0].Age=8&students[1].Age=9 + /// + /// In this case the elements of the collection are identified in the input data set by an incrementing + /// integer index. + /// + /// + /// or: + /// + /// + /// Model: IDictionary<string, int> + /// Query String: ?students[0].Key=Joey&students[0].Value=8 + /// + /// In this case the dictionary is treated as a collection of key-value pairs, and the elements of the + /// collection are identified in the input data set by an incrementing integer index. + /// + /// + /// Using this key format, the enumerator enumerates model objects of type matching + /// . The indices of the elements in the collection are used to + /// compute the model prefix keys. + /// + public class DefaultCollectionValidationStrategy : IValidationStrategy + { + private static readonly MethodInfo _getEnumerator = typeof(DefaultCollectionValidationStrategy) + .GetMethod(nameof(GetEnumerator), BindingFlags.Static | BindingFlags.NonPublic); + + /// + /// Gets an instance of . + /// + public static readonly DefaultCollectionValidationStrategy Instance = new DefaultCollectionValidationStrategy(); + private readonly ConcurrentDictionary> _genericGetEnumeratorCache = new ConcurrentDictionary>(); + + private DefaultCollectionValidationStrategy() + { + } + + /// + public IEnumerator GetChildren( + ModelMetadata metadata, + string key, + object model) + { + var enumerator = GetEnumeratorForElementType(metadata, model); + return new Enumerator(metadata.ElementMetadata, key, enumerator); + } + + public IEnumerator GetEnumeratorForElementType(ModelMetadata metadata, object model) + { + Func getEnumerator = _genericGetEnumeratorCache.GetOrAdd( + key: metadata.ElementType, + valueFactory: (type) => { + var getEnumeratorMethod = _getEnumerator.MakeGenericMethod(type); + var parameter = Expression.Parameter(typeof(object), "model"); + var expression = + Expression.Lambda>( + Expression.Call(null, getEnumeratorMethod, parameter), + parameter); + return expression.Compile(); + }); + + return getEnumerator(model); + } + + // Called via reflection. + private static IEnumerator GetEnumerator(object model) + { + return (model as IEnumerable)?.GetEnumerator() ?? ((IEnumerable)model).GetEnumerator(); + } + + private class Enumerator : IEnumerator + { + private readonly string _key; + private readonly ModelMetadata _metadata; + private readonly IEnumerator _enumerator; + + private ValidationEntry _entry; + private int _index; + + public Enumerator( + ModelMetadata metadata, + string key, + IEnumerator enumerator) + { + _metadata = metadata; + _key = key; + _enumerator = enumerator; + + _index = -1; + } + + public ValidationEntry Current => _entry; + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + _index++; + if (!_enumerator.MoveNext()) + { + return false; + } + + var key = ModelNames.CreateIndexModelName(_key, _index); + var model = _enumerator.Current; + + _entry = new ValidationEntry(_metadata, key, model); + + return true; + } + + public void Dispose() + { + } + + public void Reset() + { + _enumerator.Reset(); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultComplexObjectValidationStrategy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultComplexObjectValidationStrategy.cs new file mode 100644 index 0000000000..a2a1e0323a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultComplexObjectValidationStrategy.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.Collections; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// The default implementation of for a complex object. + /// + public class DefaultComplexObjectValidationStrategy : IValidationStrategy + { + private static readonly bool IsMono = Type.GetType("Mono.Runtime") != null; + + /// + /// Gets an instance of . + /// + public static readonly IValidationStrategy Instance = new DefaultComplexObjectValidationStrategy(); + + private DefaultComplexObjectValidationStrategy() + { + } + + /// + public IEnumerator GetChildren( + ModelMetadata metadata, + string key, + object model) + { + return new Enumerator(metadata.Properties, key, model); + } + + private class Enumerator : IEnumerator + { + private readonly string _key; + private readonly object _model; + private readonly ModelPropertyCollection _properties; + + private ValidationEntry _entry; + private int _index; + + public Enumerator( + ModelPropertyCollection properties, + string key, + object model) + { + _properties = properties; + _key = key; + _model = model; + + _index = -1; + } + + public ValidationEntry Current => _entry; + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + _index++; + if (_index >= _properties.Count) + { + return false; + } + + var property = _properties[_index]; + var propertyName = property.BinderModelName ?? property.PropertyName; + var key = ModelNames.CreatePropertyModelName(_key, propertyName); + + if (_model == null) + { + // Performance: Never create a delegate when container is null. + _entry = new ValidationEntry(property, key, model: null); + } + else if (IsMono) + { + _entry = new ValidationEntry(property, key, () => GetModelOnMono(_model, property.PropertyName)); + } + else + { + _entry = new ValidationEntry(property, key, () => GetModel(_model, property)); + } + + return true; + } + + public void Dispose() + { + } + + public void Reset() + { + throw new NotImplementedException(); + } + + private static object GetModel(object container, ModelMetadata property) + { + return property.PropertyGetter(container); + } + + // Our property accessors don't work on Mono 4.0.4 - see https://github.com/aspnet/External/issues/44 + // This is a workaround for what the PropertyGetter does in the background. + private static object GetModelOnMono(object container, string propertyName) + { + var propertyInfo = container.GetType().GetRuntimeProperty(propertyName); + try + { + return propertyInfo.GetValue(container); + } + catch (TargetInvocationException ex) + { + throw ex.InnerException; + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCompositeMetadataDetailsProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCompositeMetadataDetailsProvider.cs new file mode 100644 index 0000000000..37dfed3868 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCompositeMetadataDetailsProvider.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; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A default implementation of . + /// + public class DefaultCompositeMetadataDetailsProvider : ICompositeMetadataDetailsProvider + { + private readonly IEnumerable _providers; + + /// + /// Creates a new . + /// + /// The set of instances. + public DefaultCompositeMetadataDetailsProvider(IEnumerable providers) + { + _providers = providers; + } + + /// + public virtual void CreateBindingMetadata(BindingMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + foreach (var provider in _providers.OfType()) + { + provider.CreateBindingMetadata(context); + } + } + + /// + public virtual void CreateDisplayMetadata(DisplayMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + foreach (var provider in _providers.OfType()) + { + provider.CreateDisplayMetadata(context); + } + } + + /// + public virtual void CreateValidationMetadata(ValidationMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + foreach (var provider in _providers.OfType()) + { + provider.CreateValidationMetadata(context); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultControllerPropertyActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultControllerPropertyActivator.cs new file mode 100644 index 0000000000..857a4b23cb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultControllerPropertyActivator.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; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class DefaultControllerPropertyActivator : IControllerPropertyActivator + { + private static readonly Func[]> _getPropertiesToActivate = + GetPropertiesToActivate; + private object _initializeLock = new object(); + private bool _initialized; + private ConcurrentDictionary[]> _activateActions; + + public void Activate(ControllerContext context, object controller) + { + LazyInitializer.EnsureInitialized( + ref _activateActions, + ref _initialized, + ref _initializeLock); + + var controllerType = controller.GetType(); + var propertiesToActivate = _activateActions.GetOrAdd( + controllerType, + _getPropertiesToActivate); + + for (var i = 0; i < propertiesToActivate.Length; i++) + { + var activateInfo = propertiesToActivate[i]; + activateInfo.Activate(controller, context); + } + } + + public Action GetActivatorDelegate(ControllerActionDescriptor actionDescriptor) + { + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + var controllerType = actionDescriptor.ControllerTypeInfo?.AsType(); + if (controllerType == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(actionDescriptor.ControllerTypeInfo), + nameof(actionDescriptor)), + nameof(actionDescriptor)); + } + + var propertiesToActivate = GetPropertiesToActivate(controllerType); + void Activate(ControllerContext controllerContext, object controller) + { + for (var i = 0; i < propertiesToActivate.Length; i++) + { + var activateInfo = propertiesToActivate[i]; + activateInfo.Activate(controller, controllerContext); + } + } + + return Activate; + } + + private static PropertyActivator[] GetPropertiesToActivate(Type type) + { + IEnumerable> activators; + activators = PropertyActivator.GetPropertiesToActivate( + type, + typeof(ActionContextAttribute), + p => new PropertyActivator(p, c => c)); + + activators = activators.Concat(PropertyActivator.GetPropertiesToActivate( + type, + typeof(ControllerContextAttribute), + p => new PropertyActivator(p, c => c))); + + return activators.ToArray(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultFilterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultFilterProvider.cs new file mode 100644 index 0000000000..3e7f685494 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultFilterProvider.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.Diagnostics; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class DefaultFilterProvider : IFilterProvider + { + public int Order => -1000; + + /// + public void OnProvidersExecuting(FilterProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.ActionContext.ActionDescriptor.FilterDescriptors != null) + { + // Perf: Avoid allocations + for (var i = 0; i < context.Results.Count; i++) + { + ProvideFilter(context, context.Results[i]); + } + } + } + + /// + public void OnProvidersExecuted(FilterProviderContext context) + { + } + + public virtual void ProvideFilter(FilterProviderContext context, FilterItem filterItem) + { + if (filterItem.Filter != null) + { + return; + } + + var filter = filterItem.Descriptor.Filter; + + var filterFactory = filter as IFilterFactory; + if (filterFactory == null) + { + filterItem.Filter = filter; + filterItem.IsReusable = true; + } + else + { + var services = context.ActionContext.HttpContext.RequestServices; + filterItem.Filter = filterFactory.CreateInstance(services); + filterItem.IsReusable = filterFactory.IsReusable; + + if (filterItem.Filter == null) + { + throw new InvalidOperationException(Resources.FormatTypeMethodMustReturnNotNullValue( + "CreateInstance", + typeof(IFilterFactory).Name)); + } + + ApplyFilterToContainer(filterItem.Filter, filterFactory); + } + } + + private void ApplyFilterToContainer(object actualFilter, IFilterMetadata filterMetadata) + { + Debug.Assert(actualFilter != null, "actualFilter should not be null"); + Debug.Assert(filterMetadata != null, "filterMetadata should not be null"); + + var container = actualFilter as IFilterContainer; + + if (container != null) + { + container.FilterDefinition = filterMetadata; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelBindingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelBindingContext.cs new file mode 100644 index 0000000000..0ff9292a02 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelBindingContext.cs @@ -0,0 +1,344 @@ +// Copyright (c) .NET 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.Validation; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A context that contains operating information for model binding and validation. + /// + public class DefaultModelBindingContext : ModelBindingContext + { + private static readonly IValueProvider EmptyValueProvider = new CompositeValueProvider(); + + private IValueProvider _originalValueProvider; + private ActionContext _actionContext; + private ModelStateDictionary _modelState; + private ValidationStateDictionary _validationState; + + private State _state; + private readonly Stack _stack = new Stack(); + + /// + public override ActionContext ActionContext + { + get { return _actionContext; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + _actionContext = value; + } + } + + /// + public override string FieldName + { + get { return _state.FieldName; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + _state.FieldName = value; + } + } + + /// + public override object Model + { + get { return _state.Model; } + set { _state.Model = value; } + } + + /// + public override ModelMetadata ModelMetadata + { + get { return _state.ModelMetadata; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + _state.ModelMetadata = value; + } + } + + /// + public override string ModelName + { + get { return _state.ModelName; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + _state.ModelName = value; + } + } + + /// + public override ModelStateDictionary ModelState + { + get { return _modelState; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + _modelState = value; + } + } + + /// + public override string BinderModelName + { + get { return _state.BinderModelName; } + set { _state.BinderModelName = value; } + } + + /// + public override BindingSource BindingSource + { + get { return _state.BindingSource; } + set { _state.BindingSource = value; } + } + + /// + public override bool IsTopLevelObject + { + get { return _state.IsTopLevelObject; } + set { _state.IsTopLevelObject = value; } + } + + /// + /// Gets or sets the original value provider to be used when value providers are not filtered. + /// + public IValueProvider OriginalValueProvider + { + get { return _originalValueProvider; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + _originalValueProvider = value; + } + } + + /// + public override IValueProvider ValueProvider + { + get { return _state.ValueProvider; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + _state.ValueProvider = value; + } + } + + /// + public override Func PropertyFilter + { + get { return _state.PropertyFilter; } + set { _state.PropertyFilter = value; } + } + + /// + public override ValidationStateDictionary ValidationState + { + get { return _validationState; } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + _validationState = value; + } + } + + /// + public override ModelBindingResult Result + { + get + { + return _state.Result; + } + set + { + _state.Result = value; + } + } + + /// + /// Creates a new for top-level model binding operation. + /// + /// + /// The associated with the binding operation. + /// + /// The to use for binding. + /// associated with the model. + /// associated with the model. + /// The name of the property or parameter being bound. + /// A new instance of . + public static ModelBindingContext CreateBindingContext( + ActionContext actionContext, + IValueProvider valueProvider, + ModelMetadata metadata, + BindingInfo bindingInfo, + string modelName) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (metadata == null) + { + throw new ArgumentNullException(nameof(metadata)); + } + + if (modelName == null) + { + throw new ArgumentNullException(nameof(modelName)); + } + + var binderModelName = bindingInfo?.BinderModelName ?? metadata.BinderModelName; + var propertyFilterProvider = bindingInfo?.PropertyFilterProvider ?? metadata.PropertyFilterProvider; + + var bindingSource = bindingInfo?.BindingSource ?? metadata.BindingSource; + + return new DefaultModelBindingContext() + { + ActionContext = actionContext, + BinderModelName = binderModelName, + BindingSource = bindingSource, + PropertyFilter = propertyFilterProvider?.PropertyFilter, + + // Because this is the top-level context, FieldName and ModelName should be the same. + FieldName = binderModelName ?? modelName, + ModelName = binderModelName ?? modelName, + + IsTopLevelObject = true, + ModelMetadata = metadata, + ModelState = actionContext.ModelState, + + OriginalValueProvider = valueProvider, + ValueProvider = FilterValueProvider(valueProvider, bindingSource), + + ValidationState = new ValidationStateDictionary(), + }; + } + + /// + public override NestedScope EnterNestedScope( + ModelMetadata modelMetadata, + string fieldName, + string modelName, + object model) + { + if (modelMetadata == null) + { + throw new ArgumentNullException(nameof(modelMetadata)); + } + + if (fieldName == null) + { + throw new ArgumentNullException(nameof(fieldName)); + } + + if (modelName == null) + { + throw new ArgumentNullException(nameof(modelName)); + } + + var scope = EnterNestedScope(); + + // Only filter if the new BindingSource affects the value providers. Otherwise we want + // to preserve the current state. + if (modelMetadata.BindingSource != null && !modelMetadata.BindingSource.IsGreedy) + { + ValueProvider = FilterValueProvider(OriginalValueProvider, modelMetadata.BindingSource); + } + + Model = model; + ModelMetadata = modelMetadata; + ModelName = modelName; + FieldName = fieldName; + BinderModelName = modelMetadata.BinderModelName; + BindingSource = modelMetadata.BindingSource; + PropertyFilter = modelMetadata.PropertyFilterProvider?.PropertyFilter; + + IsTopLevelObject = false; + + return scope; + } + + /// + public override NestedScope EnterNestedScope() + { + _stack.Push(_state); + + Result = default; + + return new NestedScope(this); + } + + /// + protected override void ExitNestedScope() + { + _state = _stack.Pop(); + } + + private static IValueProvider FilterValueProvider(IValueProvider valueProvider, BindingSource bindingSource) + { + if (bindingSource == null || bindingSource.IsGreedy) + { + return valueProvider; + } + + if (!(valueProvider is IBindingSourceValueProvider bindingSourceValueProvider)) + { + return valueProvider; + } + + return bindingSourceValueProvider.Filter(bindingSource) ?? EmptyValueProvider; + } + + private struct State + { + public string FieldName; + public object Model; + public ModelMetadata ModelMetadata; + public string ModelName; + + public IValueProvider ValueProvider; + public Func PropertyFilter; + + public string BinderModelName; + public BindingSource BindingSource; + public bool IsTopLevelObject; + + public ModelBindingResult Result; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelValidatorProvider.cs new file mode 100644 index 0000000000..11d5739c02 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelValidatorProvider.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 Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A default . + /// + /// + /// The provides validators from + /// instances in . + /// + public class DefaultModelValidatorProvider : IModelValidatorProvider + { + /// + public void CreateValidators(ModelValidatorProviderContext context) + { + //Perf: Avoid allocations here + for (var i = 0; i < context.Results.Count; i++) + { + var validatorItem = context.Results[i]; + + // Don't overwrite anything that was done by a previous provider. + if (validatorItem.Validator != null) + { + continue; + } + + var validator = validatorItem.ValidatorMetadata as IModelValidator; + if (validator != null) + { + validatorItem.Validator = validator; + validatorItem.IsReusable = true; + } + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultObjectValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultObjectValidator.cs new file mode 100644 index 0000000000..0afcdb0f5e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultObjectValidator.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// The default implementation of . + /// + public class DefaultObjectValidator : ObjectModelValidator + { + /// + /// Initializes a new instance of . + /// + /// The . + /// The list of . + public DefaultObjectValidator( + IModelMetadataProvider modelMetadataProvider, + IList validatorProviders) + : base(modelMetadataProvider, validatorProviders) + { + } + + public override ValidationVisitor GetValidationVisitor( + ActionContext actionContext, + IModelValidatorProvider validatorProvider, + ValidatorCache validatorCache, + IModelMetadataProvider metadataProvider, + ValidationStateDictionary validationState) + { + return new ValidationVisitor( + actionContext, + validatorProvider, + validatorCache, + metadataProvider, + validationState); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultValidationMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultValidationMetadataProvider.cs new file mode 100644 index 0000000000..03faa4be62 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultValidationMetadataProvider.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET 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.ModelBinding.Metadata; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A default implementation of . + /// + public class DefaultValidationMetadataProvider : IValidationMetadataProvider + { + /// + public void CreateValidationMetadata(ValidationMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + foreach (var attribute in context.Attributes) + { + if (attribute is IModelValidator || attribute is IClientModelValidator) + { + // If another provider has already added this attribute, do not repeat it. + // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and + // IClientModelValidator) to be added to the ValidationMetadata twice. + // This is to ensure we do not end up with duplication validation rules on the client side. + if (!context.ValidationMetadata.ValidatorMetadata.Contains(attribute)) + { + context.ValidationMetadata.ValidatorMetadata.Add(attribute); + } + } + } + + // IPropertyValidationFilter attributes on a type affect properties in that type, not properties that have + // that type. Thus, we ignore context.TypeAttributes for properties and not check at all for types. + if (context.Key.MetadataKind == ModelMetadataKind.Property) + { + var validationFilter = context.PropertyAttributes.OfType().FirstOrDefault(); + if (validationFilter == null) + { + // No IPropertyValidationFilter attributes on the property. + // Check if container has such an attribute. + validationFilter = context.Key.ContainerType.GetTypeInfo() + .GetCustomAttributes(inherit: true) + .OfType() + .FirstOrDefault(); + } + + context.ValidationMetadata.PropertyValidationFilter = validationFilter; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitFilter.cs new file mode 100644 index 0000000000..51595bbaa6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitFilter.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A filter that sets + /// to null. + /// + public class DisableRequestSizeLimitFilter : IAuthorizationFilter, IRequestSizePolicy + { + private readonly ILogger _logger; + + /// + /// Creates a new instance of . + /// + public DisableRequestSizeLimitFilter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + /// + /// Sets the + /// to null. + /// + /// The . + /// If is not enabled or is read-only, + /// the is not applied. + public void OnAuthorization(AuthorizationFilterContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var effectivePolicy = context.FindEffectivePolicy(); + if (effectivePolicy != null && effectivePolicy != this) + { + _logger.NotMostEffectiveFilter(GetType(), effectivePolicy.GetType(), typeof(IRequestSizePolicy)); + return; + } + + var maxRequestBodySizeFeature = context.HttpContext.Features.Get(); + + if (maxRequestBodySizeFeature == null) + { + _logger.FeatureNotFound(); + } + else if (maxRequestBodySizeFeature.IsReadOnly) + { + _logger.FeatureIsReadOnly(); + } + else + { + maxRequestBodySizeFeature.MaxRequestBodySize = null; + _logger.RequestBodySizeLimitDisabled(); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ElementalValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ElementalValueProvider.cs new file mode 100644 index 0000000000..83bfeed66a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ElementalValueProvider.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 System.Globalization; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ElementalValueProvider : IValueProvider + { + public ElementalValueProvider(string key, string value, CultureInfo culture) + { + Key = key; + Value = value; + Culture = culture; + } + + public CultureInfo Culture { get; } + + public string Key { get; } + + public string Value { get; } + + public bool ContainsPrefix(string prefix) + { + return ModelStateDictionary.StartsWithPrefix(prefix, Key); + } + + public ValueProviderResult GetValue(string key) + { + if (string.Equals(key, Key, StringComparison.OrdinalIgnoreCase)) + { + return new ValueProviderResult(Value, Culture); + } + else + { + return ValueProviderResult.None; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ExplicitIndexCollectionValidationStrategy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ExplicitIndexCollectionValidationStrategy.cs new file mode 100644 index 0000000000..eb955d6964 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ExplicitIndexCollectionValidationStrategy.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; +using System.Collections; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// An implementation of for a collection bound using 'explicit indexing' + /// style keys. + /// + /// + /// This implementation handles cases like: + /// + /// Model: IList<Student> + /// Query String: ?students.index=Joey,Katherine&students[Joey].Age=8&students[Katherine].Age=9 + /// + /// In this case, 'Joey' and 'Katherine' need to be used in the model prefix keys, but cannot be inferred + /// form inspecting the collection. These prefixes are captured during model binding, and mapped to + /// the corresponding ordinal index of a model object in the collection. The enumerator returned from this + /// class will yield two 'Student' objects with corresponding keys 'students[Joey]' and 'students[Katherine]'. + /// + /// + /// Using this key format, the enumerator enumerates model objects of type matching + /// . The keys captured during model binding are mapped to the elements + /// in the collection to compute the model prefix keys. + /// + public class ExplicitIndexCollectionValidationStrategy : IValidationStrategy + { + /// + /// Creates a new . + /// + /// The keys of collection elements that were used during model binding. + public ExplicitIndexCollectionValidationStrategy(IEnumerable elementKeys) + { + if (elementKeys == null) + { + throw new ArgumentNullException(nameof(elementKeys)); + } + + ElementKeys = elementKeys; + } + + /// + /// Gets the keys of collection elements that were used during model binding. + /// + public IEnumerable ElementKeys { get; } + + /// + public IEnumerator GetChildren( + ModelMetadata metadata, + string key, + object model) + { + var enumerator = DefaultCollectionValidationStrategy.Instance.GetEnumeratorForElementType(metadata, model); + return new Enumerator(metadata.ElementMetadata, key, ElementKeys, enumerator); + } + + private class Enumerator : IEnumerator + { + private readonly string _key; + private readonly ModelMetadata _metadata; + private readonly IEnumerator _enumerator; + private readonly IEnumerator _keyEnumerator; + + private ValidationEntry _entry; + + public Enumerator( + ModelMetadata metadata, + string key, + IEnumerable elementKeys, + IEnumerator enumerator) + { + _metadata = metadata; + _key = key; + + _keyEnumerator = elementKeys.GetEnumerator(); + _enumerator = enumerator; + } + + public ValidationEntry Current => _entry; + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + if (!_keyEnumerator.MoveNext()) + { + return false; + } + + if (!_enumerator.MoveNext()) + { + return false; + } + + var model = _enumerator.Current; + var key = ModelNames.CreateIndexModelName(_key, _keyEnumerator.Current); + + _entry = new ValidationEntry(_metadata, key, model); + + return true; + } + + public void Dispose() + { + } + + public void Reset() + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursor.cs new file mode 100644 index 0000000000..b297712bd0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursor.cs @@ -0,0 +1,64 @@ +// Copyright (c) .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.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A one-way cursor for filters. + /// + /// + /// This will iterate the filter collection once per-stage, and skip any filters that don't have + /// the one of interfaces that applies to the current stage. + /// + /// Filters are always executed in the following order, but short circuiting plays a role. + /// + /// Indentation reflects nesting. + /// + /// 1. Exception Filters + /// 2. Authorization Filters + /// 3. Action Filters + /// Action + /// + /// 4. Result Filters + /// Result + /// + /// + public struct FilterCursor + { + private readonly IFilterMetadata[] _filters; + private int _index; + + public FilterCursor(IFilterMetadata[] filters) + { + _filters = filters; + _index = 0; + } + + public void Reset() + { + _index = 0; + } + + public FilterCursorItem GetNextFilter() + where TFilter : class + where TFilterAsync : class + { + while (_index < _filters.Length) + { + var filter = _filters[_index] as TFilter; + var filterAsync = _filters[_index] as TFilterAsync; + + _index += 1; + + if (filter != null || filterAsync != null) + { + return new FilterCursorItem(filter, filterAsync); + } + } + + return default(FilterCursorItem); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursorItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursorItem.cs new file mode 100644 index 0000000000..ccb42dc3f7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursorItem.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.Internal +{ + public struct FilterCursorItem + { + public FilterCursorItem(TFilter filter, TFilterAsync filterAsync) + { + Filter = filter; + FilterAsync = filterAsync; + } + + public TFilter Filter { get; } + + public TFilterAsync FilterAsync { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterDescriptorOrderComparer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterDescriptorOrderComparer.cs new file mode 100644 index 0000000000..daadc073d4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterDescriptorOrderComparer.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 System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class FilterDescriptorOrderComparer : IComparer + { + public static FilterDescriptorOrderComparer Comparer { get; } = new FilterDescriptorOrderComparer(); + + public int Compare(FilterDescriptor x, FilterDescriptor y) + { + if (x == null) + { + throw new ArgumentNullException(nameof(x)); + } + + if (y == null) + { + throw new ArgumentNullException(nameof(y)); + } + + if (x.Order == y.Order) + { + return x.Scope.CompareTo(y.Scope); + } + else + { + return x.Order.CompareTo(y.Order); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactory.cs new file mode 100644 index 0000000000..4f53acfef5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactory.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; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public static class FilterFactory + { + public static FilterFactoryResult GetAllFilters( + IFilterProvider[] filterProviders, + ActionContext actionContext) + { + if (filterProviders == null) + { + throw new ArgumentNullException(nameof(filterProviders)); + } + + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + var actionDescriptor = actionContext.ActionDescriptor; + + var staticFilterItems = new FilterItem[actionDescriptor.FilterDescriptors.Count]; + + var orderedFilters = actionDescriptor.FilterDescriptors + .OrderBy( + filter => filter, + FilterDescriptorOrderComparer.Comparer) + .ToList(); + + for (var i = 0; i < orderedFilters.Count; i++) + { + staticFilterItems[i] = new FilterItem(orderedFilters[i]); + } + + var allFilterItems = new List(staticFilterItems); + + // Execute the filter factory to determine which static filters can be cached. + var filters = CreateUncachedFiltersCore(filterProviders, actionContext, allFilterItems); + + // Cache the filter items based on the following criteria + // 1. Are created statically (ex: via filter attributes, added to global filter list etc.) + // 2. Are re-usable + for (var i = 0; i < staticFilterItems.Length; i++) + { + var item = staticFilterItems[i]; + if (!item.IsReusable) + { + item.Filter = null; + } + } + + return new FilterFactoryResult(staticFilterItems, filters); + } + + public static IFilterMetadata[] CreateUncachedFilters( + IFilterProvider[] filterProviders, + ActionContext actionContext, + FilterItem[] cachedFilterItems) + { + if (filterProviders == null) + { + throw new ArgumentNullException(nameof(filterProviders)); + } + + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + if (cachedFilterItems == null) + { + throw new ArgumentNullException(nameof(cachedFilterItems)); + } + + // Deep copy the cached filter items as filter providers could modify them + var filterItems = new List(cachedFilterItems.Length); + for (var i = 0; i < cachedFilterItems.Length; i++) + { + var filterItem = cachedFilterItems[i]; + filterItems.Add( + new FilterItem(filterItem.Descriptor) + { + Filter = filterItem.Filter, + IsReusable = filterItem.IsReusable + }); + } + + return CreateUncachedFiltersCore(filterProviders, actionContext, filterItems); + } + + private static IFilterMetadata[] CreateUncachedFiltersCore( + IFilterProvider[] filterProviders, + ActionContext actionContext, + List filterItems) + { + // Execute providers + var context = new FilterProviderContext(actionContext, filterItems); + + for (var i = 0; i < filterProviders.Length; i++) + { + filterProviders[i].OnProvidersExecuting(context); + } + + for (var i = filterProviders.Length - 1; i >= 0; i--) + { + filterProviders[i].OnProvidersExecuted(context); + } + + // Extract filter instances from statically defined filters and filter providers + var count = 0; + for (var i = 0; i < filterItems.Count; i++) + { + if (filterItems[i].Filter != null) + { + count++; + } + } + + if (count == 0) + { + return Array.Empty(); + } + else + { + var filters = new IFilterMetadata[count]; + var filterIndex = 0; + for (int i = 0; i < filterItems.Count; i++) + { + var filter = filterItems[i].Filter; + if (filter != null) + { + filters[filterIndex++] = filter; + } + } + + return filters; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactoryResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactoryResult.cs new file mode 100644 index 0000000000..e532a08427 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactoryResult.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 Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public struct FilterFactoryResult + { + public FilterFactoryResult( + FilterItem[] cacheableFilters, + IFilterMetadata[] filters) + { + CacheableFilters = cacheableFilters; + Filters = filters; + } + + public FilterItem[] CacheableFilters { get; } + + public IFilterMetadata[] Filters { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpMethodActionConstraint.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpMethodActionConstraint.cs new file mode 100644 index 0000000000..e310493486 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpMethodActionConstraint.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 System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class HttpMethodActionConstraint : IActionConstraint + { + public static readonly int HttpMethodConstraintOrder = 100; + + private readonly IReadOnlyList _httpMethods; + + // Empty collection means any method will be accepted. + public HttpMethodActionConstraint(IEnumerable httpMethods) + { + if (httpMethods == null) + { + throw new ArgumentNullException(nameof(httpMethods)); + } + + var methods = new List(); + + foreach (var method in httpMethods) + { + if (string.IsNullOrEmpty(method)) + { + throw new ArgumentException("httpMethod cannot be null or empty"); + } + + methods.Add(method); + } + + _httpMethods = new ReadOnlyCollection(methods); + } + + public IEnumerable HttpMethods => _httpMethods; + + public int Order => HttpMethodConstraintOrder; + + public virtual bool Accept(ActionConstraintContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (_httpMethods.Count == 0) + { + return true; + } + + var request = context.RouteContext.HttpContext.Request; + var method = request.Method; + + for (var i = 0; i < _httpMethods.Count; i++) + { + var supportedMethod = _httpMethods[i]; + if (string.Equals(supportedMethod, method, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpParseResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpParseResult.cs new file mode 100644 index 0000000000..84e76f08d4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpParseResult.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.Formatters.Internal +{ + public enum HttpParseResult + { + Parsed, + NotParsed, + InvalidFormat, + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpTokenParsingRules.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpTokenParsingRules.cs new file mode 100644 index 0000000000..a93f26d916 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpTokenParsingRules.cs @@ -0,0 +1,274 @@ +// Copyright (c) .NET 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.Contracts; +using System.Text; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Internal +{ + public static class HttpTokenParsingRules + { + private static readonly bool[] TokenChars; + private const int MaxNestedCount = 5; + + internal const char CR = '\r'; + internal const char LF = '\n'; + internal const char SP = ' '; + internal const char Tab = '\t'; + internal const int MaxInt64Digits = 19; + internal const int MaxInt32Digits = 10; + + // iso-8859-1, Western European (ISO) + internal static readonly Encoding DefaultHttpEncoding = Encoding.GetEncoding(28591); + + static HttpTokenParsingRules() + { + // token = 1* + // CTL = + + TokenChars = new bool[128]; // everything is false + + for (int i = 33; i < 127; i++) // skip Space (32) & DEL (127) + { + TokenChars[i] = true; + } + + // remove separators: these are not valid token characters + TokenChars[(byte)'('] = false; + TokenChars[(byte)')'] = false; + TokenChars[(byte)'<'] = false; + TokenChars[(byte)'>'] = false; + TokenChars[(byte)'@'] = false; + TokenChars[(byte)','] = false; + TokenChars[(byte)';'] = false; + TokenChars[(byte)':'] = false; + TokenChars[(byte)'\\'] = false; + TokenChars[(byte)'"'] = false; + TokenChars[(byte)'/'] = false; + TokenChars[(byte)'['] = false; + TokenChars[(byte)']'] = false; + TokenChars[(byte)'?'] = false; + TokenChars[(byte)'='] = false; + TokenChars[(byte)'{'] = false; + TokenChars[(byte)'}'] = false; + } + + internal static bool IsTokenChar(char character) + { + // Must be between 'space' (32) and 'DEL' (127) + if (character > 127) + { + return false; + } + + return TokenChars[character]; + } + + internal static int GetTokenLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + while (current < input.Length) + { + if (!IsTokenChar(input[current])) + { + return current - startIndex; + } + current++; + } + return input.Length - startIndex; + } + + internal static int GetWhitespaceLength(string input, int startIndex) + { + Contract.Requires(input != null); + Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex))); + + if (startIndex >= input.Length) + { + return 0; + } + + var current = startIndex; + + while (current < input.Length) + { + var c = input[current]; + + if ((c == SP) || (c == Tab)) + { + current++; + continue; + } + + if (c == CR) + { + // If we have a #13 char, it must be followed by #10 and then at least one SP or HT. + if ((current + 2 < input.Length) && (input[current + 1] == LF)) + { + char spaceOrTab = input[current + 2]; + if ((spaceOrTab == SP) || (spaceOrTab == Tab)) + { + current += 3; + continue; + } + } + } + + return current - startIndex; + } + + // All characters between startIndex and the end of the string are LWS characters. + return input.Length - startIndex; + } + + internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length) + { + var nestedCount = 0; + return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length); + } + + // quoted-pair = "\" CHAR + // CHAR = + internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) && + (Contract.ValueAtReturn(out length) <= (input.Length - startIndex))); + + length = 0; + + if (input[startIndex] != '\\') + { + return HttpParseResult.NotParsed; + } + + // Quoted-char has 2 characters. Check whether there are 2 chars left ('\' + char) + // If so, check whether the character is in the range 0-127. If not, it's an invalid value. + if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127)) + { + return HttpParseResult.InvalidFormat; + } + + // We don't care what the char next to '\' is. + length = 2; + return HttpParseResult.Parsed; + } + + // TEXT = + // LWS = [CRLF] 1*( SP | HT ) + // CTL = + // + // Since we don't really care about the content of a quoted string or comment, we're more tolerant and + // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment). + // + // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like + // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested + // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any) + // is unusual. + private static HttpParseResult GetExpressionLength( + string input, + int startIndex, + char openChar, + char closeChar, + bool supportsNesting, + ref int nestedCount, + out int length) + { + Contract.Requires(input != null); + Contract.Requires((startIndex >= 0) && (startIndex < input.Length)); + Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) || + (Contract.ValueAtReturn(out length) > 0)); + + length = 0; + + if (input[startIndex] != openChar) + { + return HttpParseResult.NotParsed; + } + + var current = startIndex + 1; // Start parsing with the character next to the first open-char + while (current < input.Length) + { + // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e. + // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char. + if ((current + 2 < input.Length) && + (GetQuotedPairLength(input, current, out var quotedPairLength) == HttpParseResult.Parsed)) + { + // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair, + // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only + // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars). + current = current + quotedPairLength; + continue; + } + + // If we support nested expressions and we find an open-char, then parse the nested expressions. + if (supportsNesting && (input[current] == openChar)) + { + nestedCount++; + try + { + // Check if we exceeded the number of nested calls. + if (nestedCount > MaxNestedCount) + { + return HttpParseResult.InvalidFormat; + } + + var nestedResult = GetExpressionLength( + input, + current, + openChar, + closeChar, + supportsNesting, + ref nestedCount, + out var nestedLength); + + switch (nestedResult) + { + case HttpParseResult.Parsed: + current += nestedLength; // add the length of the nested expression and continue. + break; + + case HttpParseResult.NotParsed: + Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression " + + "parsing, because we found the open-char. So either it's a valid nested " + + "expression or it has invalid format."); + break; + + case HttpParseResult.InvalidFormat: + // If the nested expression is invalid, we can't continue, so we fail with invalid format. + return HttpParseResult.InvalidFormat; + + default: + Contract.Assert(false, "Unknown enum result: " + nestedResult); + break; + } + } + finally + { + nestedCount--; + } + } + + if (input[current] == closeChar) + { + length = current - startIndex + 1; + return HttpParseResult.Parsed; + } + current++; + } + + // We didn't see the final quote, therefore we have an invalid expression string. + return HttpParseResult.InvalidFormat; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IApiBehaviorMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IApiBehaviorMetadata.cs new file mode 100644 index 0000000000..b73c1ba56e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IApiBehaviorMetadata.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.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// An interface for . See + /// for details. + /// + public interface IApiBehaviorMetadata : IFilterMetadata + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs new file mode 100644 index 0000000000..b004cbdcf3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.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.ActionConstraints; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// An constraint that identifies a type which can be used to select an action + /// based on incoming request. + /// + public interface IConsumesActionConstraint : IActionConstraint + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IControllerPropertyActivatorFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IControllerPropertyActivatorFactory.cs new file mode 100644 index 0000000000..441dee9898 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IControllerPropertyActivatorFactory.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; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public interface IControllerPropertyActivator + { + void Activate(ControllerContext context, object controller); + + Action GetActivatorDelegate(ControllerActionDescriptor actionDescriptor); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IFormatFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IFormatFilter.cs new file mode 100644 index 0000000000..911ca2d247 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IFormatFilter.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.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Internal +{ + /// + /// A filter that produces the desired content type for the request. + /// + public interface IFormatFilter : IFilterMetadata + { + /// + /// Gets the format value for the request associated with the provided . + /// + /// The associated with the current request. + /// A format value, or null if a format cannot be determined for the request. + string GetFormat(ActionContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IMiddlewareFilterFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IMiddlewareFilterFeature.cs new file mode 100644 index 0000000000..43af488b8a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IMiddlewareFilterFeature.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 Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A feature in which is used to capture the + /// currently executing context of a resource filter. This feature is used in the final middleware + /// of a middleware filter's pipeline to keep the request flow through the rest of the MVC layers. + /// + public interface IMiddlewareFilterFeature + { + ResourceExecutingContext ResourceExecutingContext { get; } + + ResourceExecutionDelegate ResourceExecutionDelegate { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IParameterInfoParameterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IParameterInfoParameterDescriptor.cs new file mode 100644 index 0000000000..6f260b5750 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/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.Internal +{ + /// + /// A for action parameters. + /// + public interface IParameterInfoParameterDescriptor + { + /// + /// Gets the . + /// + ParameterInfo ParameterInfo { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IPropertyInfoParameterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IPropertyInfoParameterDescriptor.cs new file mode 100644 index 0000000000..ccbe5de73c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/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.Internal +{ + /// + /// A for bound properties. + /// + public interface IPropertyInfoParameterDescriptor + { + /// + /// Gets the . + /// + PropertyInfo PropertyInfo { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IResponseCacheFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IResponseCacheFilter.cs new file mode 100644 index 0000000000..5b53c254dd --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IResponseCacheFilter.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.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A filter which sets the appropriate headers related to Response caching. + /// + public interface IResponseCacheFilter : IFilterMetadata + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ITypeActivatorCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ITypeActivatorCache.cs new file mode 100644 index 0000000000..a097b29910 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ITypeActivatorCache.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; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// Caches instances produced by + /// . + /// + public interface ITypeActivatorCache + { + /// + /// Creates an instance of . + /// + /// The used to resolve dependencies for + /// . + /// The of the to create. + TInstance CreateInstance(IServiceProvider serviceProvider, Type optionType); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MediaTypeSegmentWithQuality.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MediaTypeSegmentWithQuality.cs new file mode 100644 index 0000000000..c9a8f4502e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MediaTypeSegmentWithQuality.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.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Internal +{ + /// + /// A media type with its associated quality. + /// + public struct MediaTypeSegmentWithQuality + { + /// + /// Initializes an instance of . + /// + /// The containing the media type. + /// The quality parameter of the media type or 1 in the case it does not exist. + public MediaTypeSegmentWithQuality(StringSegment mediaType, double quality) + { + MediaType = mediaType; + Quality = quality; + } + + /// + /// Gets the media type of this . + /// + public StringSegment MediaType { get; } + + /// + /// Gets the quality of this . + /// + public double Quality { get; } + + /// + public override string ToString() + { + // For logging purposes + return MediaType.ToString(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpRequestStreamReaderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpRequestStreamReaderFactory.cs new file mode 100644 index 0000000000..88f6f8428c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpRequestStreamReaderFactory.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; +using System.Buffers; +using System.IO; +using System.Text; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.WebUtilities; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// An that uses pooled buffers. + /// + public class MemoryPoolHttpRequestStreamReaderFactory : IHttpRequestStreamReaderFactory + { + /// + /// The default size of created char buffers. + /// + public static readonly int DefaultBufferSize = 1024; // 1KB - results in a 4KB byte array for UTF8. + + private readonly ArrayPool _bytePool; + private readonly ArrayPool _charPool; + + /// + /// Creates a new . + /// + /// + /// The for creating buffers. + /// + /// + /// The for creating buffers. + /// + public MemoryPoolHttpRequestStreamReaderFactory( + ArrayPool bytePool, + ArrayPool charPool) + { + if (bytePool == null) + { + throw new ArgumentNullException(nameof(bytePool)); + } + + if (charPool == null) + { + throw new ArgumentNullException(nameof(charPool)); + } + + _bytePool = bytePool; + _charPool = charPool; + } + + /// + public TextReader CreateReader(Stream stream, Encoding encoding) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + return new HttpRequestStreamReader(stream, encoding, DefaultBufferSize, _bytePool, _charPool); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpResponseStreamWriterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpResponseStreamWriterFactory.cs new file mode 100644 index 0000000000..91dd31e0c7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpResponseStreamWriterFactory.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 System; +using System.Buffers; +using System.IO; +using System.Text; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.WebUtilities; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// An that uses pooled buffers. + /// + public class MemoryPoolHttpResponseStreamWriterFactory : IHttpResponseStreamWriterFactory + { + /// + /// The default size of buffers s will allocate. + /// + /// + /// 16K causes each to allocate one 16K + /// array and one 32K (for UTF8) array. + /// + /// + /// maintains s + /// for these arrays. + /// + public static readonly int DefaultBufferSize = 16 * 1024; + + private readonly ArrayPool _bytePool; + private readonly ArrayPool _charPool; + + /// + /// Creates a new . + /// + /// + /// The for creating buffers. + /// + /// + /// The for creating buffers. + /// + public MemoryPoolHttpResponseStreamWriterFactory( + ArrayPool bytePool, + ArrayPool charPool) + { + if (bytePool == null) + { + throw new ArgumentNullException(nameof(bytePool)); + } + + if (charPool == null) + { + throw new ArgumentNullException(nameof(charPool)); + } + + _bytePool = bytePool; + _charPool = charPool; + } + + /// + public TextWriter CreateWriter(Stream stream, Encoding encoding) + { + if (stream == null) + { + throw new ArgumentNullException(nameof(stream)); + } + + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + return new HttpResponseStreamWriter(stream, encoding, DefaultBufferSize, _bytePool, _charPool); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilter.cs new file mode 100644 index 0000000000..11ac93d5ab --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilter.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; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A filter which executes a user configured middleware pipeline. + /// + internal class MiddlewareFilter : IAsyncResourceFilter + { + private readonly RequestDelegate _middlewarePipeline; + + public MiddlewareFilter(RequestDelegate middlewarePipeline) + { + if (middlewarePipeline == null) + { + throw new ArgumentNullException(nameof(middlewarePipeline)); + } + + _middlewarePipeline = middlewarePipeline; + } + + public Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next) + { + var httpContext = context.HttpContext; + + // Capture the current context into the feature. This will later be used in the end middleware to continue + // the execution flow to later MVC layers. + // Example: + // this filter -> user-middleware1 -> user-middleware2 -> the-end-middleware -> resource filters or model binding + var feature = new MiddlewareFilterFeature() + { + ResourceExecutionDelegate = next, + ResourceExecutingContext = context + }; + httpContext.Features.Set(feature); + + return _middlewarePipeline(httpContext); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs new file mode 100644 index 0000000000..cbceab72b0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs @@ -0,0 +1,95 @@ +// Copyright (c) .NET 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 Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Core; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// Builds a middleware pipeline after receiving the pipeline from a pipeline provider + /// + public class MiddlewareFilterBuilder + { + // 'GetOrAdd' call on the dictionary is not thread safe and we might end up creating the pipeline more + // once. To prevent this Lazy<> is used. In the worst case multiple Lazy<> objects are created for multiple + // threads but only one of the objects succeeds in creating a pipeline. + private readonly ConcurrentDictionary> _pipelinesCache + = new ConcurrentDictionary>(); + private readonly MiddlewareFilterConfigurationProvider _configurationProvider; + + public IApplicationBuilder ApplicationBuilder { get; set; } + + public MiddlewareFilterBuilder(MiddlewareFilterConfigurationProvider configurationProvider) + { + _configurationProvider = configurationProvider; + } + + public RequestDelegate GetPipeline(Type configurationType) + { + // Build the pipeline only once. This is similar to how middlewares registered in Startup are constructed. + + var requestDelegate = _pipelinesCache.GetOrAdd( + configurationType, + key => new Lazy(() => BuildPipeline(key))); + + return requestDelegate.Value; + } + + private RequestDelegate BuildPipeline(Type middlewarePipelineProviderType) + { + if (ApplicationBuilder == null) + { + throw new InvalidOperationException( + Resources.FormatMiddlewareFilterBuilder_NullApplicationBuilder(nameof(ApplicationBuilder))); + } + + var nestedAppBuilder = ApplicationBuilder.New(); + + // Get the 'Configure' method from the user provided type. + var configureDelegate = _configurationProvider.CreateConfigureDelegate(middlewarePipelineProviderType); + configureDelegate(nestedAppBuilder); + + // The middleware resource filter, after receiving the request executes the user configured middleware + // pipeline. Since we want execution of the request to continue to later MVC layers (resource filters + // or model binding), add a middleware at the end of the user provided pipeline which make sure to continue + // this flow. + // Example: + // middleware filter -> user-middleware1 -> user-middleware2 -> end-middleware -> resource filters or model binding + nestedAppBuilder.Run(async (httpContext) => + { + var feature = httpContext.Features.Get(); + if (feature == null) + { + throw new InvalidOperationException( + Resources.FormatMiddlewareFilterBuilder_NoMiddlewareFeature(nameof(IMiddlewareFilterFeature))); + } + + var resourceExecutionDelegate = feature.ResourceExecutionDelegate; + + var resourceExecutedContext = await resourceExecutionDelegate(); + if (resourceExecutedContext.ExceptionHandled) + { + return; + } + + // 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 + // 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(); + if (resourceExecutedContext.Exception != null) + { + // This line is rarely reachable because ResourceInvoker captures thrown Exceptions using + // ExceptionDispatchInfo. That said, filters could set only resourceExecutedContext.Exception. + throw resourceExecutedContext.Exception; + } + }); + + return nestedAppBuilder.Build(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterConfigurationProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterConfigurationProvider.cs new file mode 100644 index 0000000000..ca05f4d6df --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterConfigurationProvider.cs @@ -0,0 +1,128 @@ +// Copyright (c) .NET 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.Builder; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// Calls into user provided 'Configure' methods for configuring a middleware pipeline. The semantics of finding + /// the 'Configure' methods is similar to the application Startup class. + /// + public class MiddlewareFilterConfigurationProvider + { + public Action CreateConfigureDelegate(Type configurationType) + { + if (configurationType == null) + { + throw new ArgumentNullException(nameof(configurationType)); + } + + if (!HasParameterlessConstructor(configurationType.GetTypeInfo())) + { + throw new InvalidOperationException( + Resources.FormatMiddlewareFilterConfigurationProvider_CreateConfigureDelegate_CannotCreateType(configurationType, nameof(configurationType))); + } + + var instance = Activator.CreateInstance(configurationType); + var configureDelegateBuilder = GetConfigureDelegateBuilder(configurationType); + return configureDelegateBuilder.Build(instance); + } + + private static ConfigureBuilder GetConfigureDelegateBuilder(Type startupType) + { + var configureMethod = FindMethod(startupType, typeof(void)); + return new ConfigureBuilder(configureMethod); + } + + private static MethodInfo FindMethod(Type startupType, Type returnType = null) + { + var methodName = "Configure"; + + var methods = startupType.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static); + var selectedMethods = methods.Where(method => method.Name.Equals(methodName)).ToList(); + if (selectedMethods.Count > 1) + { + throw new InvalidOperationException( + Resources.FormatMiddewareFilter_ConfigureMethodOverload(methodName)); + } + + var methodInfo = selectedMethods.FirstOrDefault(); + if (methodInfo == null) + { + throw new InvalidOperationException( + Resources.FormatMiddewareFilter_NoConfigureMethod( + methodName, + startupType.FullName)); + } + + if (returnType != null && methodInfo.ReturnType != returnType) + { + throw new InvalidOperationException( + Resources.FormatMiddlewareFilter_InvalidConfigureReturnType( + methodInfo.Name, + startupType.FullName, + returnType.Name)); + } + return methodInfo; + } + + private static bool HasParameterlessConstructor(TypeInfo modelTypeInfo) + { + return !modelTypeInfo.IsAbstract && modelTypeInfo.GetConstructor(Type.EmptyTypes) != null; + } + + private class ConfigureBuilder + { + public ConfigureBuilder(MethodInfo configure) + { + MethodInfo = configure; + } + + public MethodInfo MethodInfo { get; } + + public Action Build(object instance) + { + return (applicationBuilder) => Invoke(instance, applicationBuilder); + } + + private void Invoke(object instance, IApplicationBuilder builder) + { + var serviceProvider = builder.ApplicationServices; + var parameterInfos = MethodInfo.GetParameters(); + var parameters = new object[parameterInfos.Length]; + for (var index = 0; index < parameterInfos.Length; index++) + { + var parameterInfo = parameterInfos[index]; + if (parameterInfo.ParameterType == typeof(IApplicationBuilder)) + { + parameters[index] = builder; + } + else + { + try + { + parameters[index] = serviceProvider.GetRequiredService(parameterInfo.ParameterType); + } + catch (Exception ex) + { + throw new InvalidOperationException( + Resources.FormatMiddlewareFilter_ServiceResolutionFail( + parameterInfo.ParameterType.FullName, + parameterInfo.Name, + MethodInfo.Name, + MethodInfo.DeclaringType.FullName), + ex); + } + } + } + MethodInfo.Invoke(instance, parameters); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterFeature.cs new file mode 100644 index 0000000000..d33378d6f7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterFeature.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.Filters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class MiddlewareFilterFeature : IMiddlewareFilterFeature + { + public ResourceExecutingContext ResourceExecutingContext { get; set; } + + public ResourceExecutionDelegate ResourceExecutionDelegate { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcAttributeRouteHandler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcAttributeRouteHandler.cs new file mode 100644 index 0000000000..93f7277522 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcAttributeRouteHandler.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 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; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class MvcAttributeRouteHandler : IRouter + { + private readonly IActionContextAccessor _actionContextAccessor; + private readonly IActionInvokerFactory _actionInvokerFactory; + private readonly IActionSelector _actionSelector; + private readonly ILogger _logger; + private DiagnosticSource _diagnosticSource; + + public MvcAttributeRouteHandler( + IActionInvokerFactory actionInvokerFactory, + IActionSelector actionSelector, + DiagnosticSource diagnosticSource, + ILoggerFactory loggerFactory) + : this(actionInvokerFactory, actionSelector, diagnosticSource, loggerFactory, actionContextAccessor: null) + { + } + + public MvcAttributeRouteHandler( + IActionInvokerFactory actionInvokerFactory, + IActionSelector actionSelector, + DiagnosticSource diagnosticSource, + ILoggerFactory loggerFactory, + IActionContextAccessor actionContextAccessor) + { + // The IActionContextAccessor is optional. We want to avoid the overhead of using CallContext + // if possible. + _actionContextAccessor = actionContextAccessor; + + _actionInvokerFactory = actionInvokerFactory; + _actionSelector = actionSelector; + _diagnosticSource = diagnosticSource; + _logger = loggerFactory.CreateLogger(); + } + + public ActionDescriptor[] Actions { get; set; } + + public VirtualPathData GetVirtualPath(VirtualPathContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // We return null here because we're not responsible for generating the url, the route is. + return null; + } + + public Task RouteAsync(RouteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (Actions == null) + { + var message = Resources.FormatPropertyOfTypeCannotBeNull( + nameof(Actions), + nameof(MvcAttributeRouteHandler)); + throw new InvalidOperationException(message); + } + + var actionDescriptor = _actionSelector.SelectBestCandidate(context, Actions); + if (actionDescriptor == null) + { + _logger.NoActionsMatched(context.RouteData.Values); + return Task.CompletedTask; + } + + foreach (var kvp in actionDescriptor.RouteValues) + { + if (!string.IsNullOrEmpty(kvp.Value)) + { + context.RouteData.Values[kvp.Key] = kvp.Value; + } + } + + context.Handler = (c) => + { + var routeData = c.GetRouteData(); + + var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor); + if (_actionContextAccessor != null) + { + _actionContextAccessor.ActionContext = actionContext; + } + + var invoker = _actionInvokerFactory.CreateInvoker(actionContext); + if (invoker == null) + { + throw new InvalidOperationException( + Resources.FormatActionInvokerFactory_CouldNotCreateInvoker( + actionDescriptor.DisplayName)); + } + + return invoker.InvokeAsync(); + }; + + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcBuilder.cs new file mode 100644 index 0000000000..d2a0db532e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcBuilder.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.Mvc.ApplicationParts; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// Allows fine grained configuration of MVC services. + /// + public class MvcBuilder : IMvcBuilder + { + /// + /// Initializes a new instance. + /// + /// The to add services to. + /// The of the application. + public MvcBuilder(IServiceCollection services, ApplicationPartManager manager) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (manager == null) + { + throw new ArgumentNullException(nameof(manager)); + } + + Services = services; + PartManager = manager; + } + + /// + public IServiceCollection Services { get; } + + /// + public ApplicationPartManager PartManager { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreBuilder.cs new file mode 100644 index 0000000000..89a9897afd --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreBuilder.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.AspNetCore.Mvc.ApplicationParts; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// Allows fine grained configuration of essential MVC services. + /// + public class MvcCoreBuilder : IMvcCoreBuilder + { + /// + /// Initializes a new instance. + /// + /// The to add services to. + /// The of the application. + public MvcCoreBuilder( + IServiceCollection services, + ApplicationPartManager manager) + { + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + if (manager == null) + { + throw new ArgumentNullException(nameof(manager)); + } + + Services = services; + PartManager = manager; + } + + /// + public ApplicationPartManager PartManager { get; } + + /// + public IServiceCollection Services { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreDiagnosticSourceExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreDiagnosticSourceExtensions.cs new file mode 100644 index 0000000000..d5bee24ba7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreDiagnosticSourceExtensions.cs @@ -0,0 +1,712 @@ +// Copyright (c) .NET 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.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + // We're doing a lot of asserts here because these methods are really tedious to test and + // highly dependent on the details of the invoker's state machine. Basically if we wrote the + // obvious unit tests that would generate a lot of boilerplate and wouldn't cover the hard parts. + public static class MvcCoreDiagnosticSourceExtensions + { + public static void BeforeAction( + this DiagnosticSource diagnosticSource, + ActionDescriptor actionDescriptor, + HttpContext httpContext, + RouteData routeData) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionDescriptor != null); + Debug.Assert(httpContext != null); + Debug.Assert(routeData != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeAction")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeAction", + new { actionDescriptor, httpContext = httpContext, routeData = routeData }); + } + } + + public static void AfterAction( + this DiagnosticSource diagnosticSource, + ActionDescriptor actionDescriptor, + HttpContext httpContext, + RouteData routeData) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionDescriptor != null); + Debug.Assert(httpContext != null); + Debug.Assert(routeData != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterAction")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterAction", + new { actionDescriptor, httpContext = httpContext, routeData = routeData }); + } + } + + public static void BeforeOnAuthorizationAsync( + this DiagnosticSource diagnosticSource, + AuthorizationFilterContext authorizationContext, + IAsyncAuthorizationFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(authorizationContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnAuthorization")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnAuthorization", + new + { + actionDescriptor = authorizationContext.ActionDescriptor, + authorizationContext = authorizationContext, + filter = filter + }); + } + } + + public static void AfterOnAuthorizationAsync( + this DiagnosticSource diagnosticSource, + AuthorizationFilterContext authorizationContext, + IAsyncAuthorizationFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(authorizationContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnAuthorization")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnAuthorization", + new + { + actionDescriptor = authorizationContext.ActionDescriptor, + authorizationContext = authorizationContext, + filter = filter + }); + } + } + + public static void BeforeOnAuthorization( + this DiagnosticSource diagnosticSource, + AuthorizationFilterContext authorizationContext, + IAuthorizationFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(authorizationContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnAuthorization")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnAuthorization", + new + { + actionDescriptor = authorizationContext.ActionDescriptor, + authorizationContext = authorizationContext, + filter = filter + }); + } + } + + public static void AfterOnAuthorization( + this DiagnosticSource diagnosticSource, + AuthorizationFilterContext authorizationContext, + IAuthorizationFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(authorizationContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnAuthorization")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnAuthorization", + new + { + actionDescriptor = authorizationContext.ActionDescriptor, + authorizationContext = authorizationContext, + filter = filter + }); + } + } + + public static void BeforeOnResourceExecution( + this DiagnosticSource diagnosticSource, + ResourceExecutingContext resourceExecutingContext, + IAsyncResourceFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resourceExecutingContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnResourceExecution")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnResourceExecution", + new + { + actionDescriptor = resourceExecutingContext.ActionDescriptor, + resourceExecutingContext = resourceExecutingContext, + filter = filter + }); + } + } + + public static void AfterOnResourceExecution( + this DiagnosticSource diagnosticSource, + ResourceExecutedContext resourceExecutedContext, + IAsyncResourceFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resourceExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnResourceExecution")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnResourceExecution", + new + { + actionDescriptor = resourceExecutedContext.ActionDescriptor, + resourceExecutedContext = resourceExecutedContext, + filter = filter + }); + } + } + + public static void BeforeOnResourceExecuting( + this DiagnosticSource diagnosticSource, + ResourceExecutingContext resourceExecutingContext, + IResourceFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resourceExecutingContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnResourceExecuting")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnResourceExecuting", + new + { + actionDescriptor = resourceExecutingContext.ActionDescriptor, + resourceExecutingContext = resourceExecutingContext, + filter = filter + }); + } + } + + public static void AfterOnResourceExecuting( + this DiagnosticSource diagnosticSource, + ResourceExecutingContext resourceExecutingContext, + IResourceFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resourceExecutingContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnResourceExecuting")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnResourceExecuting", + new + { + actionDescriptor = resourceExecutingContext.ActionDescriptor, + resourceExecutingContext = resourceExecutingContext, + filter = filter + }); + } + } + + public static void BeforeOnResourceExecuted( + this DiagnosticSource diagnosticSource, + ResourceExecutedContext resourceExecutedContext, + IResourceFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resourceExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnResourceExecuted")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnResourceExecuted", + new + { + actionDescriptor = resourceExecutedContext.ActionDescriptor, + resourceExecutedContext = resourceExecutedContext, + filter = filter + }); + } + } + + public static void AfterOnResourceExecuted( + this DiagnosticSource diagnosticSource, + ResourceExecutedContext resourceExecutedContext, + IResourceFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resourceExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnResourceExecuted")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnResourceExecuted", + new + { + actionDescriptor = resourceExecutedContext.ActionDescriptor, + resourceExecutedContext = resourceExecutedContext, + filter = filter + }); + } + } + + public static void BeforeOnExceptionAsync( + this DiagnosticSource diagnosticSource, + ExceptionContext exceptionContext, + IAsyncExceptionFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(exceptionContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnException")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnException", + new + { + actionDescriptor = exceptionContext.ActionDescriptor, + exceptionContext = exceptionContext, + filter = filter + }); + } + } + + public static void AfterOnExceptionAsync( + this DiagnosticSource diagnosticSource, + ExceptionContext exceptionContext, + IAsyncExceptionFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(exceptionContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnException")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnException", + new + { + actionDescriptor = exceptionContext.ActionDescriptor, + exceptionContext = exceptionContext, + filter = filter + }); + } + } + + public static void BeforeOnException( + this DiagnosticSource diagnosticSource, + ExceptionContext exceptionContext, + IExceptionFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(exceptionContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnException")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnException", + new + { + actionDescriptor = exceptionContext.ActionDescriptor, + exceptionContext = exceptionContext, + filter = filter + }); + } + } + + public static void AfterOnException( + this DiagnosticSource diagnosticSource, + ExceptionContext exceptionContext, + IExceptionFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(exceptionContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnException")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnException", + new + { + actionDescriptor = exceptionContext.ActionDescriptor, + exceptionContext = exceptionContext, + filter = filter + }); + } + } + + public static void BeforeOnActionExecution( + this DiagnosticSource diagnosticSource, + ActionExecutingContext actionExecutingContext, + IAsyncActionFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionExecutingContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnActionExecution")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnActionExecution", + new + { + actionDescriptor = actionExecutingContext.ActionDescriptor, + actionExecutingContext = actionExecutingContext, + filter = filter + }); + } + } + + public static void AfterOnActionExecution( + this DiagnosticSource diagnosticSource, + ActionExecutedContext actionExecutedContext, + IAsyncActionFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnActionExecution")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnActionExecution", + new + { + actionDescriptor = actionExecutedContext.ActionDescriptor, + actionExecutedContext = actionExecutedContext, + filter = filter + }); + } + } + + public static void BeforeOnActionExecuting( + this DiagnosticSource diagnosticSource, + ActionExecutingContext actionExecutingContext, + IActionFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionExecutingContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnActionExecuting")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnActionExecuting", + new + { + actionDescriptor = actionExecutingContext.ActionDescriptor, + actionExecutingContext = actionExecutingContext, + filter = filter + }); + } + } + + public static void AfterOnActionExecuting( + this DiagnosticSource diagnosticSource, + ActionExecutingContext actionExecutingContext, + IActionFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionExecutingContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnActionExecuting")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnActionExecuting", + new + { + actionDescriptor = actionExecutingContext.ActionDescriptor, + actionExecutingContext = actionExecutingContext, + filter = filter + }); + } + } + + public static void BeforeOnActionExecuted( + this DiagnosticSource diagnosticSource, + ActionExecutedContext actionExecutedContext, + IActionFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnActionExecuted")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnActionExecuted", + new + { + actionDescriptor = actionExecutedContext.ActionDescriptor, + actionExecutedContext = actionExecutedContext, + filter = filter + }); + } + } + + public static void AfterOnActionExecuted( + this DiagnosticSource diagnosticSource, + ActionExecutedContext actionExecutedContext, + IActionFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnActionExecuted")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnActionExecuted", + new + { + actionDescriptor = actionExecutedContext.ActionDescriptor, + actionExecutedContext = actionExecutedContext, + filter = filter + }); + } + } + + public static void BeforeActionMethod( + this DiagnosticSource diagnosticSource, + ActionContext actionContext, + IDictionary actionArguments, + object controller) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionContext != null); + Debug.Assert(actionArguments != null); + Debug.Assert(controller != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeActionMethod")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeActionMethod", + new + { + actionContext = actionContext, + arguments = actionArguments, + controller = controller + }); + } + } + + public static void AfterActionMethod( + this DiagnosticSource diagnosticSource, + ActionContext actionContext, + IDictionary actionArguments, + object controller, + IActionResult result) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionContext != null); + Debug.Assert(actionArguments != null); + Debug.Assert(controller != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterActionMethod")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterActionMethod", + new + { + actionContext = actionContext, + arguments = actionArguments, + controller = controller, + result = result + }); + } + } + + public static void BeforeOnResultExecution( + this DiagnosticSource diagnosticSource, + ResultExecutingContext resultExecutingContext, + IAsyncResultFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resultExecutingContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnResultExecution")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnResultExecution", + new + { + actionDescriptor = resultExecutingContext.ActionDescriptor, + resultExecutingContext = resultExecutingContext, + filter = filter + }); + } + } + + public static void AfterOnResultExecution( + this DiagnosticSource diagnosticSource, + ResultExecutedContext resultExecutedContext, + IAsyncResultFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resultExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnResultExecution")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnResultExecution", + new + { + actionDescriptor = resultExecutedContext.ActionDescriptor, + resultExecutedContext = resultExecutedContext, + filter = filter + }); + } + } + + public static void BeforeOnResultExecuting( + this DiagnosticSource diagnosticSource, + ResultExecutingContext resultExecutingContext, + IResultFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resultExecutingContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnResultExecuting")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnResultExecuting", + new + { + actionDescriptor = resultExecutingContext.ActionDescriptor, + resultExecutingContext = resultExecutingContext, + filter = filter + }); + } + } + + public static void AfterOnResultExecuting( + this DiagnosticSource diagnosticSource, + ResultExecutingContext resultExecutingContext, + IResultFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resultExecutingContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnResultExecuting")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnResultExecuting", + new + { + actionDescriptor = resultExecutingContext.ActionDescriptor, + resultExecutingContext = resultExecutingContext, + filter = filter + }); + } + } + + public static void BeforeOnResultExecuted( + this DiagnosticSource diagnosticSource, + ResultExecutedContext resultExecutedContext, + IResultFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resultExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnResultExecuted")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnResultExecuted", + new + { + actionDescriptor = resultExecutedContext.ActionDescriptor, + resultExecutedContext = resultExecutedContext, + filter = filter + }); + } + } + + public static void AfterOnResultExecuted( + this DiagnosticSource diagnosticSource, + ResultExecutedContext resultExecutedContext, + IResultFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(resultExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnResultExecuted")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnResultExecuted", + new + { + actionDescriptor = resultExecutedContext.ActionDescriptor, + resultExecutedContext = resultExecutedContext, + filter = filter + }); + } + } + + public static void BeforeActionResult( + this DiagnosticSource diagnosticSource, + ActionContext actionContext, + IActionResult result) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionContext != null); + Debug.Assert(result != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeActionResult")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeActionResult", + new { actionContext = actionContext, result = result }); + } + } + + public static void AfterActionResult( + this DiagnosticSource diagnosticSource, + ActionContext actionContext, + IActionResult result) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionContext != null); + Debug.Assert(result != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterActionResult")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterActionResult", + new { actionContext = actionContext, result = result }); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs new file mode 100644 index 0000000000..c840f785b1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs @@ -0,0 +1,1587 @@ +// Copyright (c) .NET 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.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Security.Claims; +using System.Text; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Formatters.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + internal static class MvcCoreLoggerExtensions + { + public const string ActionFilter = "Action Filter"; + private static readonly string[] _noFilters = new[] { "None" }; + + private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; + + private static readonly Action _actionExecuting; + private static readonly Action _actionExecuted; + + private static readonly Action _challengeResultExecuting; + + private static readonly Action _contentResultExecuting; + + private static readonly Action _actionMethodExecuting; + private static readonly Action _actionMethodExecutingWithArguments; + private static readonly Action _actionMethodExecuted; + + private static readonly Action _logFilterExecutionPlan; + private static readonly Action _beforeExecutingMethodOnFilter; + private static readonly Action _afterExecutingMethodOnFilter; + private static readonly Action _beforeExecutingActionResult; + private static readonly Action _afterExecutingActionResult; + + private static readonly Action _ambiguousActions; + private static readonly Action _constraintMismatch; + + private static readonly Action _executingFileResult; + private static readonly Action _executingFileResultWithNoFileName; + private static readonly Action _notEnabledForRangeProcessing; + private static readonly Action _writingRangeToBody; + private static readonly Action _authorizationFailure; + private static readonly Action _resourceFilterShortCircuit; + private static readonly Action _resultFilterShortCircuit; + private static readonly Action _actionFilterShortCircuit; + private static readonly Action _exceptionFilterShortCircuit; + + private static readonly Action _forbidResultExecuting; + private static readonly Action _signInResultExecuting; + private static readonly Action _signOutResultExecuting; + + private static readonly Action _httpStatusCodeResultExecuting; + + private static readonly Action _localRedirectResultExecuting; + + private static readonly Action _objectResultExecuting; + private static readonly Action _noFormatter; + private static readonly Action _formatterSelected; + private static readonly Action _skippedContentNegotiation; + private static readonly Action _noAcceptForNegotiation; + private static readonly Action, Exception> _noFormatterFromNegotiation; + + private static readonly Action _inputFormatterSelected; + private static readonly Action _inputFormatterRejected; + private static readonly Action _noInputFormatterSelected; + private static readonly Action _removeFromBodyAttribute; + + private static readonly Action _redirectResultExecuting; + + private static readonly Action _redirectToActionResultExecuting; + + private static readonly Action _redirectToRouteResultExecuting; + + private static readonly Action _noActionsMatched; + + private static readonly Action _redirectToPageResultExecuting; + + private static readonly Action _featureNotFound; + private static readonly Action _featureIsReadOnly; + private static readonly Action _maxRequestBodySizeSet; + private static readonly Action _requestBodySizeLimitDisabled; + + private static readonly Action _cannotApplyRequestFormLimits; + private static readonly Action _appliedRequestFormLimits; + + private static readonly Action _modelStateInvalidFilterExecuting; + + private static readonly Action _inferredParameterSource; + private static readonly Action _registeredModelBinderProviders; + private static readonly Action _foundNoValueForPropertyInRequest; + private static readonly Action _foundNoValueForParameterInRequest; + private static readonly Action _foundNoValueInRequest; + private static readonly Action _noPublicSettableProperties; + private static readonly Action _cannotBindToComplexType; + private static readonly Action _cannotBindToFilesCollectionDueToUnsupportedContentType; + private static readonly Action _cannotCreateHeaderModelBinder; + private static readonly Action _cannotCreateHeaderModelBinderCompatVersion_2_0; + private static readonly Action _noFilesFoundInRequest; + private static readonly Action _noNonIndexBasedFormatFoundForCollection; + private static readonly Action _attemptingToBindCollectionUsingIndices; + private static readonly Action _attemptingToBindCollectionOfKeyValuePair; + private static readonly Action _noKeyValueFormatForDictionaryModelBinder; + private static readonly Action _attemptingToBindParameterModel; + private static readonly Action _doneAttemptingToBindParameterModel; + private static readonly Action _attemptingToBindPropertyModel; + private static readonly Action _doneAttemptingToBindPropertyModel; + private static readonly Action _attemptingToBindModel; + private static readonly Action _doneAttemptingToBindModel; + private static readonly Action _attemptingToBindParameter; + private static readonly Action _doneAttemptingToBindParameter; + private static readonly Action _attemptingToBindProperty; + private static readonly Action _doneAttemptingToBindProperty; + private static readonly Action _attemptingToValidateProperty; + private static readonly Action _doneAttemptingToValidateProperty; + private static readonly Action _attemptingToValidateParameter; + private static readonly Action _doneAttemptingToValidateParameter; + private static readonly Action _unsupportedFormatFilterContentType; + private static readonly Action _actionDoesNotSupportFormatFilterContentType; + private static readonly Action _cannotApplyFormatFilterContentType; + private static readonly Action _actionDoesNotExplicitlySpecifyContentTypes; + private static readonly Action, Exception> _selectingOutputFormatterUsingAcceptHeader; + private static readonly Action _ifMatchPreconditionFailed; + private static readonly Action _ifUnmodifiedSincePreconditionFailed; + private static readonly Action _ifRangeLastModifiedPreconditionFailed; + private static readonly Action _ifRangeETagPreconditionFailed; + private static readonly Action, MediaTypeCollection, Exception> _selectingOutputFormatterUsingAcceptHeaderAndExplicitContentTypes; + private static readonly Action _selectingOutputFormatterWithoutUsingContentTypes; + private static readonly Action _selectingOutputFormatterUsingContentTypes; + private static readonly Action _selectingFirstCanWriteFormatter; + private static readonly Action _notMostEffectiveFilter; + private static readonly Action, Exception> _registeredOutputFormatters; + + static MvcCoreLoggerExtensions() + { + _actionExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Route matched with {RouteData}. Executing action {ActionName}"); + + _actionExecuted = LoggerMessage.Define( + LogLevel.Information, + 2, + "Executed action {ActionName} in {ElapsedMilliseconds}ms"); + + _challengeResultExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing ChallengeResult with authentication schemes ({Schemes})."); + + _contentResultExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing ContentResult with HTTP Response ContentType of {ContentType}"); + + _actionMethodExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing action method {ActionName} - Validation state: {ValidationState}"); + + _actionMethodExecutingWithArguments = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing action method {ActionName} with arguments ({Arguments}) - Validation state: {ValidationState}"); + + _actionMethodExecuted = LoggerMessage.Define( + LogLevel.Information, + 2, + "Executed action method {ActionName}, returned result {ActionResult} in {ElapsedMilliseconds}ms."); + + _logFilterExecutionPlan = LoggerMessage.Define( + LogLevel.Debug, + 1, + "Execution plan of {FilterType} filters (in the following order): {Filters}"); + + _beforeExecutingMethodOnFilter = LoggerMessage.Define( + LogLevel.Trace, + 2, + "{FilterType}: Before executing {Method} on filter {Filter}."); + + _afterExecutingMethodOnFilter = LoggerMessage.Define( + LogLevel.Trace, + 3, + "{FilterType}: After executing {Method} on filter {Filter}."); + + _beforeExecutingActionResult = LoggerMessage.Define( + LogLevel.Trace, + 4, + "Before executing action result {ActionResult}."); + + _afterExecutingActionResult = LoggerMessage.Define( + LogLevel.Trace, + 5, + "After executing action result {ActionResult}."); + + _ambiguousActions = LoggerMessage.Define( + LogLevel.Error, + 1, + "Request matched multiple actions resulting in ambiguity. Matching actions: {AmbiguousActions}"); + + _constraintMismatch = LoggerMessage.Define( + LogLevel.Debug, + 2, + "Action '{ActionName}' with id '{ActionId}' did not match the constraint '{ActionConstraint}'"); + + _executingFileResult = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing {FileResultType}, sending file '{FileDownloadPath}' with download name '{FileDownloadName}' ..."); + + _executingFileResultWithNoFileName = LoggerMessage.Define( + LogLevel.Information, + 2, + "Executing {FileResultType}, sending file with download name '{FileDownloadName}' ..."); + + _authorizationFailure = LoggerMessage.Define( + LogLevel.Information, + 3, + "Authorization failed for the request at filter '{AuthorizationFilter}'."); + + _resourceFilterShortCircuit = LoggerMessage.Define( + LogLevel.Debug, + 4, + "Request was short circuited at resource filter '{ResourceFilter}'."); + + _resultFilterShortCircuit = LoggerMessage.Define( + LogLevel.Debug, + 5, + "Request was short circuited at result filter '{ResultFilter}'."); + + _actionFilterShortCircuit = LoggerMessage.Define( + LogLevel.Debug, + 3, + "Request was short circuited at action filter '{ActionFilter}'."); + + _exceptionFilterShortCircuit = LoggerMessage.Define( + LogLevel.Debug, + 4, + "Request was short circuited at exception filter '{ExceptionFilter}'."); + + _forbidResultExecuting = LoggerMessage.Define( + LogLevel.Information, + eventId: 1, + formatString: $"Executing {nameof(ForbidResult)} with authentication schemes ({{Schemes}})."); + + _signInResultExecuting = LoggerMessage.Define( + LogLevel.Information, + eventId: 1, + formatString: $"Executing {nameof(SignInResult)} with authentication scheme ({{Scheme}}) and the following principal: {{Principal}}."); + + _signOutResultExecuting = LoggerMessage.Define( + LogLevel.Information, + eventId: 1, + formatString: $"Executing {nameof(SignOutResult)} with authentication schemes ({{Schemes}})."); + + _httpStatusCodeResultExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing HttpStatusCodeResult, setting HTTP status code {StatusCode}"); + + _localRedirectResultExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing LocalRedirectResult, redirecting to {Destination}."); + + _noFormatter = LoggerMessage.Define( + LogLevel.Warning, + 1, + "No output formatter was found for content type '{ContentType}' to write the response."); + + _objectResultExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing ObjectResult, writing value of type '{Type}'."); + + _formatterSelected = LoggerMessage.Define( + LogLevel.Debug, + 2, + "Selected output formatter '{OutputFormatter}' and content type '{ContentType}' to write the response."); + + _skippedContentNegotiation = LoggerMessage.Define( + LogLevel.Debug, + 3, + "Skipped content negotiation as content type '{ContentType}' is explicitly set for the response."); + + _noAcceptForNegotiation = LoggerMessage.Define( + LogLevel.Debug, + 4, + "No information found on request to perform content negotiation."); + + _noFormatterFromNegotiation = LoggerMessage.Define>( + LogLevel.Debug, + 5, + "Could not find an output formatter based on content negotiation. Accepted types were ({AcceptTypes})"); + + _inputFormatterSelected = LoggerMessage.Define( + LogLevel.Debug, + 1, + "Selected input formatter '{InputFormatter}' for content type '{ContentType}'."); + + _inputFormatterRejected = LoggerMessage.Define( + LogLevel.Debug, + 2, + "Rejected input formatter '{InputFormatter}' for content type '{ContentType}'."); + + _noInputFormatterSelected = LoggerMessage.Define( + LogLevel.Debug, + 3, + "No input formatter was found to support the content type '{ContentType}' for use with the [FromBody] attribute."); + + _removeFromBodyAttribute = LoggerMessage.Define( + LogLevel.Debug, + 4, + "To use model binding, remove the [FromBody] attribute from the property or parameter named '{ModelName}' with model type '{ModelType}'."); + + _redirectResultExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing RedirectResult, redirecting to {Destination}."); + + _redirectToActionResultExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing RedirectResult, redirecting to {Destination}."); + + _redirectToRouteResultExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing RedirectToRouteResult, redirecting to {Destination} from route {RouteName}."); + + _redirectToPageResultExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing RedirectToPageResult, redirecting to {Page}."); + + _noActionsMatched = LoggerMessage.Define( + LogLevel.Debug, + 3, + "No actions matched the current request. Route values: {RouteValues}"); + + _featureNotFound = LoggerMessage.Define( + LogLevel.Warning, + 1, + "A request body size limit could not be applied. This server does not support the IHttpRequestBodySizeFeature."); + + _featureIsReadOnly = LoggerMessage.Define( + LogLevel.Warning, + 2, + "A request body size limit could not be applied. The IHttpRequestBodySizeFeature for the server is read-only."); + + _maxRequestBodySizeSet = LoggerMessage.Define( + LogLevel.Debug, + 3, + "The maximum request body size has been set to {RequestSize}."); + + _requestBodySizeLimitDisabled = LoggerMessage.Define( + LogLevel.Debug, + 3, + "The request body size limit has been disabled."); + + _cannotApplyRequestFormLimits = LoggerMessage.Define( + LogLevel.Warning, + 1, + "Unable to apply configured form options since the request form has already been read."); + + _appliedRequestFormLimits = LoggerMessage.Define( + LogLevel.Debug, + 2, + "Applied the configured form options on the current request."); + + _modelStateInvalidFilterExecuting = LoggerMessage.Define( + LogLevel.Debug, + 1, + "The request has model state errors, returning an error response."); + + _inferredParameterSource = LoggerMessage.Define( + LogLevel.Debug, + 1, + "Inferred binding source for '{ParameterName}` on `{ActionName}` as {BindingSource}."); + + _unsupportedFormatFilterContentType = LoggerMessage.Define( + LogLevel.Debug, + 1, + "Could not find a media type for the format '{FormatFilterContentType}'."); + + _actionDoesNotSupportFormatFilterContentType = LoggerMessage.Define( + LogLevel.Debug, + 2, + "Current action does not support the content type '{FormatFilterContentType}'. The supported content types are '{SupportedMediaTypes}'."); + + _cannotApplyFormatFilterContentType = LoggerMessage.Define( + LogLevel.Debug, + 3, + "Cannot apply content type '{FormatFilterContentType}' to the response as current action had explicitly set a preferred content type."); + + _notMostEffectiveFilter = LoggerMessage.Define( + LogLevel.Debug, + 4, + "Execution of filter {OverriddenFilter} is preempted by filter {OverridingFilter} which is the most effective filter implementing policy {FilterPolicy}."); + + _actionDoesNotExplicitlySpecifyContentTypes = LoggerMessage.Define( + LogLevel.Debug, + 5, + "Current action does not explicitly specify any content types for the response."); + + _selectingOutputFormatterUsingAcceptHeader = LoggerMessage.Define>( + LogLevel.Debug, + 6, + "Attempting to select an output formatter based on Accept header '{AcceptHeader}'."); + + _selectingOutputFormatterUsingAcceptHeaderAndExplicitContentTypes = LoggerMessage.Define, MediaTypeCollection>( + LogLevel.Debug, + 7, + "Attempting to select an output formatter based on Accept header '{AcceptHeader}' and explicitly specified content types '{ExplicitContentTypes}'. The content types in the accept header must be a subset of the explicitly set content types."); + + _selectingOutputFormatterWithoutUsingContentTypes = LoggerMessage.Define( + LogLevel.Debug, + 8, + "Attempting to select an output formatter without using a content type as no explicit content types were specified for the response."); + + _selectingOutputFormatterUsingContentTypes = LoggerMessage.Define( + LogLevel.Debug, + 9, + "Attempting to select the first output formatter in the output formatters list which supports a content type from the explicitly specified content types '{ExplicitContentTypes}'."); + + _selectingFirstCanWriteFormatter = LoggerMessage.Define( + LogLevel.Debug, + 10, + "Attempting to select the first formatter in the output formatters list which can write the result."); + + _registeredOutputFormatters = LoggerMessage.Define>( + LogLevel.Debug, + 11, + "List of registered output formatters, in the following order: {OutputFormatters}"); + + _writingRangeToBody = LoggerMessage.Define( + LogLevel.Debug, + 17, + "Writing the requested range of bytes to the body..."); + + _registeredModelBinderProviders = LoggerMessage.Define( + LogLevel.Debug, + 12, + "Registered model binder providers, in the following order: {ModelBinderProviders}"); + + _attemptingToBindPropertyModel = LoggerMessage.Define( + LogLevel.Debug, + 13, + "Attempting to bind property '{PropertyContainerType}.{PropertyName}' of type '{ModelType}' using the name '{ModelName}' in request data ..."); + + _doneAttemptingToBindPropertyModel = LoggerMessage.Define( + LogLevel.Debug, + 14, + "Done attempting to bind property '{PropertyContainerType}.{PropertyName}' of type '{ModelType}'."); + + _foundNoValueForPropertyInRequest = LoggerMessage.Define( + LogLevel.Debug, + 15, + "Could not find a value in the request with name '{ModelName}' for binding property '{PropertyContainerType}.{ModelFieldName}' of type '{ModelType}'."); + + _foundNoValueForParameterInRequest = LoggerMessage.Define( + LogLevel.Debug, + 16, + "Could not find a value in the request with name '{ModelName}' for binding parameter '{ModelFieldName}' of type '{ModelType}'."); + + _noPublicSettableProperties = LoggerMessage.Define( + LogLevel.Debug, + 17, + "Could not bind to model with name '{ModelName}' and type '{ModelType}' as the type has no public settable properties."); + + _cannotBindToComplexType = LoggerMessage.Define( + LogLevel.Debug, + 18, + "Could not bind to model of type '{ModelType}' as there were no values in the request for any of the properties."); + + _cannotBindToFilesCollectionDueToUnsupportedContentType = LoggerMessage.Define( + LogLevel.Debug, + 19, + "Could not bind to model with name '{ModelName}' and type '{ModelType}' as the request did not have a content type of either 'application/x-www-form-urlencoded' or 'multipart/form-data'."); + + _cannotCreateHeaderModelBinder = LoggerMessage.Define( + LogLevel.Debug, + 20, + "Could not create a binder for type '{ModelType}' as this binder only supports simple types (like string, int, bool, enum) or a collection of simple types."); + + _noFilesFoundInRequest = LoggerMessage.Define( + LogLevel.Debug, + 21, + "No files found in the request to bind the model to."); + + _attemptingToBindParameter = LoggerMessage.Define( + LogLevel.Debug, + 22, + "Attempting to bind parameter '{ParameterName}' of type '{ModelType}' ..."); + + _doneAttemptingToBindParameter = LoggerMessage.Define( + LogLevel.Debug, + 23, + "Done attempting to bind parameter '{ParameterName}' of type '{ModelType}'."); + + _attemptingToBindModel = LoggerMessage.Define( + LogLevel.Debug, + 24, + "Attempting to bind model of type '{ModelType}' using the name '{ModelName}' in request data ..."); + + _doneAttemptingToBindModel = LoggerMessage.Define( + LogLevel.Debug, + 25, + "Done attempting to bind model of type '{ModelType}' using the name '{ModelName}'."); + + _attemptingToValidateParameter = LoggerMessage.Define( + LogLevel.Debug, + 26, + "Attempting to validate the bound parameter '{ParameterName}' of type '{ModelType}' ..."); + + _doneAttemptingToValidateParameter = LoggerMessage.Define( + LogLevel.Debug, + 27, + "Done attempting to validate the bound parameter '{ParameterName}' of type '{ModelType}'."); + + _noNonIndexBasedFormatFoundForCollection = LoggerMessage.Define( + LogLevel.Debug, + 28, + "Could not bind to collection using a format like {ModelName}=value1&{ModelName}=value2"); + + _attemptingToBindCollectionUsingIndices = LoggerMessage.Define( + LogLevel.Debug, + 29, + "Attempting to bind model using indices. Example formats include: " + + "[0]=value1&[1]=value2, " + + "{ModelName}[0]=value1&{ModelName}[1]=value2, " + + "{ModelName}.index=zero&{ModelName}.index=one&{ModelName}[zero]=value1&{ModelName}[one]=value2"); + + _attemptingToBindCollectionOfKeyValuePair = LoggerMessage.Define( + LogLevel.Debug, + 30, + "Attempting to bind collection of KeyValuePair. Example formats include: " + + "[0].Key=key1&[0].Value=value1&[1].Key=key2&[1].Value=value2, " + + "{ModelName}[0].Key=key1&{ModelName}[0].Value=value1&{ModelName}[1].Key=key2&{ModelName}[1].Value=value2, " + + "{ModelName}[key1]=value1&{ModelName}[key2]=value2"); + + _noKeyValueFormatForDictionaryModelBinder = LoggerMessage.Define( + LogLevel.Debug, + 33, + "Attempting to bind model with name '{ModelName}' using the format {ModelName}[key1]=value1&{ModelName}[key2]=value2"); + + _ifMatchPreconditionFailed = LoggerMessage.Define( + LogLevel.Debug, + 34, + "Current request's If-Match header check failed as the file's current etag '{CurrentETag}' does not match with any of the supplied etags."); + + _ifUnmodifiedSincePreconditionFailed = LoggerMessage.Define( + LogLevel.Debug, + 35, + "Current request's If-Unmodified-Since header check failed as the file was modified (at '{lastModified}') after the If-Unmodified-Since date '{IfUnmodifiedSinceDate}'."); + + _ifRangeLastModifiedPreconditionFailed = LoggerMessage.Define( + LogLevel.Debug, + 36, + "Could not serve range as the file was modified (at {LastModified}) after the if-Range's last modified date '{IfRangeLastModified}'."); + + _ifRangeETagPreconditionFailed = LoggerMessage.Define( + LogLevel.Debug, + 37, + "Could not serve range as the file's current etag '{CurrentETag}' does not match the If-Range etag '{IfRangeETag}'."); + + _notEnabledForRangeProcessing = LoggerMessage.Define( + LogLevel.Debug, + 38, + $"The file result has not been enabled for processing range requests. To enable it, set the property '{nameof(FileResult.EnableRangeProcessing)}' on the result to 'true'."); + + _attemptingToBindProperty = LoggerMessage.Define( + LogLevel.Debug, + 39, + "Attempting to bind property '{PropertyContainerType}.{PropertyName}' of type '{ModelType}' ..."); + + _doneAttemptingToBindProperty = LoggerMessage.Define( + LogLevel.Debug, + 40, + "Done attempting to bind property '{PropertyContainerType}.{PropertyName}' of type '{ModelType}'."); + + _attemptingToValidateProperty = LoggerMessage.Define( + LogLevel.Debug, + 41, + "Attempting to validate the bound property '{PropertyContainerType}.{PropertyName}' of type '{ModelType}' ..."); + + _doneAttemptingToValidateProperty = LoggerMessage.Define( + LogLevel.Debug, + 42, + "Done attempting to validate the bound property '{PropertyContainerType}.{PropertyName}' of type '{ModelType}'."); + + _cannotCreateHeaderModelBinderCompatVersion_2_0 = LoggerMessage.Define( + LogLevel.Debug, + 43, + "Could not create a binder for type '{ModelType}' as this binder only supports 'System.String' type or a collection of 'System.String'."); + + _attemptingToBindParameterModel = LoggerMessage.Define( + LogLevel.Debug, + 44, + "Attempting to bind parameter '{ParameterName}' of type '{ModelType}' using the name '{ModelName}' in request data ..."); + + _doneAttemptingToBindParameterModel = LoggerMessage.Define( + LogLevel.Debug, + 45, + "Done attempting to bind parameter '{ParameterName}' of type '{ModelType}'."); + + _foundNoValueInRequest = LoggerMessage.Define( + LogLevel.Debug, + 46, + "Could not find a value in the request with name '{ModelName}' of type '{ModelType}'."); + } + + public static void RegisteredOutputFormatters(this ILogger logger, IEnumerable outputFormatters) + { + _registeredOutputFormatters(logger, outputFormatters, null); + } + + public static void SelectingOutputFormatterUsingAcceptHeaderAndExplicitContentTypes( + this ILogger logger, + IEnumerable acceptHeader, + MediaTypeCollection mediaTypeCollection) + { + _selectingOutputFormatterUsingAcceptHeaderAndExplicitContentTypes(logger, acceptHeader, mediaTypeCollection, null); + } + + public static void SelectingOutputFormatterUsingAcceptHeader(this ILogger logger, IEnumerable acceptHeader) + { + _selectingOutputFormatterUsingAcceptHeader(logger, acceptHeader, null); + } + + public static void SelectingOutputFormatterUsingContentTypes(this ILogger logger, MediaTypeCollection mediaTypeCollection) + { + _selectingOutputFormatterUsingContentTypes(logger, mediaTypeCollection, null); + } + + public static void SelectingOutputFormatterWithoutUsingContentTypes(this ILogger logger) + { + _selectingOutputFormatterWithoutUsingContentTypes(logger, null); + } + + public static void SelectFirstCanWriteFormatter(this ILogger logger) + { + _selectingFirstCanWriteFormatter(logger, null); + } + + public static IDisposable ActionScope(this ILogger logger, ActionDescriptor action) + { + return logger.BeginScope(new ActionLogScope(action)); + } + + public static void ExecutingAction(this ILogger logger, ActionDescriptor action) + { + if (logger.IsEnabled(LogLevel.Information)) + { + var routeKeys = action.RouteValues.Keys.ToArray(); + var routeValues = action.RouteValues.Values.ToArray(); + var stringBuilder = new StringBuilder(); + stringBuilder.Append("{"); + for (var i = 0; i < routeValues.Length; i++) + { + if (i == routeValues.Length - 1) + { + stringBuilder.Append($"{routeKeys[i]} = \"{routeValues[i]}\"}}"); + } + else + { + stringBuilder.Append($"{routeKeys[i]} = \"{routeValues[i]}\", "); + } + } + + _actionExecuting(logger, stringBuilder.ToString(), action.DisplayName, null); + } + } + + public static void AuthorizationFiltersExecutionPlan(this ILogger logger, IEnumerable filters) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var authorizationFilters = filters.Where(f => f is IAuthorizationFilter || f is IAsyncAuthorizationFilter); + LogFilterExecutionPlan(logger, "authorization", authorizationFilters); + } + + public static void ResourceFiltersExecutionPlan(this ILogger logger, IEnumerable filters) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var resourceFilters = filters.Where(f => f is IResourceFilter || f is IAsyncResourceFilter); + LogFilterExecutionPlan(logger, "resource", resourceFilters); + } + + public static void ActionFiltersExecutionPlan(this ILogger logger, IEnumerable filters) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var actionFilters = filters.Where(f => f is IActionFilter || f is IAsyncActionFilter); + LogFilterExecutionPlan(logger, "action", actionFilters); + } + + public static void ExceptionFiltersExecutionPlan(this ILogger logger, IEnumerable filters) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var exceptionFilters = filters.Where(f => f is IExceptionFilter || f is IAsyncExceptionFilter); + LogFilterExecutionPlan(logger, "exception", exceptionFilters); + } + + public static void ResultFiltersExecutionPlan(this ILogger logger, IEnumerable filters) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var resultFilters = filters.Where(f => f is IResultFilter || f is IAsyncResultFilter); + LogFilterExecutionPlan(logger, "result", resultFilters); + } + + public static void BeforeExecutingMethodOnFilter( + this ILogger logger, + string filterType, + string methodName, + IFilterMetadata filter) + { + _beforeExecutingMethodOnFilter(logger, filterType, methodName, filter.GetType(), null); + } + + public static void AfterExecutingMethodOnFilter( + this ILogger logger, + string filterType, + string methodName, + IFilterMetadata filter) + { + _afterExecutingMethodOnFilter(logger, filterType, methodName, filter.GetType(), null); + } + + public static void ExecutedAction(this ILogger logger, ActionDescriptor action, TimeSpan timeSpan) + { + // 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); + } + } + + public static void NoActionsMatched(this ILogger logger, IDictionary routeValueDictionary) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + string[] routeValues = null; + if (routeValueDictionary != null) + { + routeValues = routeValueDictionary + .Select(pair => pair.Key + "=" + Convert.ToString(pair.Value)) + .ToArray(); + } + _noActionsMatched(logger, routeValues, null); + } + } + + public static void ChallengeResultExecuting(this ILogger logger, IList schemes) + { + if (logger.IsEnabled(LogLevel.Information)) + { + _challengeResultExecuting(logger, schemes.ToArray(), null); + } + } + + public static void ContentResultExecuting(this ILogger logger, string contentType) + { + _contentResultExecuting(logger, contentType, null); + } + + public static void BeforeExecutingActionResult(this ILogger logger, IActionResult actionResult) + { + _beforeExecutingActionResult(logger, actionResult.GetType(), null); + } + + public static void AfterExecutingActionResult(this ILogger logger, IActionResult actionResult) + { + _afterExecutingActionResult(logger, actionResult.GetType(), null); + } + + public static void ActionMethodExecuting(this ILogger logger, ControllerContext context, object[] arguments) + { + if (logger.IsEnabled(LogLevel.Information)) + { + var actionName = context.ActionDescriptor.DisplayName; + + var validationState = context.ModelState.ValidationState; + + string[] convertedArguments; + if (arguments == null) + { + _actionMethodExecuting(logger, actionName, validationState, null); + } + else + { + convertedArguments = new string[arguments.Length]; + for (var i = 0; i < arguments.Length; i++) + { + convertedArguments[i] = Convert.ToString(arguments[i]); + } + + _actionMethodExecutingWithArguments(logger, actionName, convertedArguments, validationState, null); + } + } + } + + public static void ActionMethodExecuted(this ILogger logger, ControllerContext context, IActionResult result, TimeSpan timeSpan) + { + if (logger.IsEnabled(LogLevel.Information)) + { + var actionName = context.ActionDescriptor.DisplayName; + _actionMethodExecuted(logger, actionName, Convert.ToString(result), timeSpan.TotalMilliseconds, null); + } + } + + public static void AmbiguousActions(this ILogger logger, string actionNames) + { + _ambiguousActions(logger, actionNames, null); + } + + public static void ConstraintMismatch( + this ILogger logger, + string actionName, + string actionId, + IActionConstraint actionConstraint) + { + _constraintMismatch(logger, actionName, actionId, actionConstraint, null); + } + + public static void ExecutingFileResult(this ILogger logger, FileResult fileResult) + { + _executingFileResultWithNoFileName(logger, fileResult, fileResult.FileDownloadName, null); + } + + public static void ExecutingFileResult(this ILogger logger, FileResult fileResult, string fileName) + { + _executingFileResult(logger, fileResult, fileName, fileResult.FileDownloadName, null); + } + + public static void NotEnabledForRangeProcessing(this ILogger logger) + { + _notEnabledForRangeProcessing(logger, null); + } + + public static void WritingRangeToBody(this ILogger logger) + { + _writingRangeToBody(logger, null); + } + + public static void AuthorizationFailure( + this ILogger logger, + IFilterMetadata filter) + { + _authorizationFailure(logger, filter, null); + } + + public static void ResourceFilterShortCircuited( + this ILogger logger, + IFilterMetadata filter) + { + _resourceFilterShortCircuit(logger, filter, null); + } + + public static void ResultFilterShortCircuited( + this ILogger logger, + IFilterMetadata filter) + { + _resultFilterShortCircuit(logger, filter, null); + } + + public static void ExceptionFilterShortCircuited( + this ILogger logger, + IFilterMetadata filter) + { + _exceptionFilterShortCircuit(logger, filter, null); + } + + public static void ActionFilterShortCircuited( + this ILogger logger, + IFilterMetadata filter) + { + _actionFilterShortCircuit(logger, filter, null); + } + + public static void ForbidResultExecuting(this ILogger logger, IList authenticationSchemes) + { + if (logger.IsEnabled(LogLevel.Information)) + { + _forbidResultExecuting(logger, authenticationSchemes.ToArray(), null); + } + } + + public static void SignInResultExecuting(this ILogger logger, string authenticationScheme, ClaimsPrincipal principal) + { + _signInResultExecuting(logger, authenticationScheme, principal, null); + } + + public static void SignOutResultExecuting(this ILogger logger, IList authenticationSchemes) + { + if (logger.IsEnabled(LogLevel.Information)) + { + _signOutResultExecuting(logger, authenticationSchemes.ToArray(), null); + } + } + + public static void HttpStatusCodeResultExecuting(this ILogger logger, int statusCode) + { + _httpStatusCodeResultExecuting(logger, statusCode, null); + } + + public static void LocalRedirectResultExecuting(this ILogger logger, string destination) + { + _localRedirectResultExecuting(logger, destination, null); + } + + public static void ObjectResultExecuting(this ILogger logger, object value) + { + if (logger.IsEnabled(LogLevel.Information)) + { + var type = value == null ? "null" : value.GetType().FullName; + _objectResultExecuting(logger, type, null); + } + } + + public static void NoFormatter( + this ILogger logger, + OutputFormatterCanWriteContext formatterContext) + { + if (logger.IsEnabled(LogLevel.Warning)) + { + _noFormatter(logger, Convert.ToString(formatterContext.ContentType), null); + } + } + + public static void FormatterSelected( + this ILogger logger, + IOutputFormatter outputFormatter, + OutputFormatterCanWriteContext context) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + var contentType = Convert.ToString(context.ContentType); + _formatterSelected(logger, outputFormatter, contentType, null); + } + } + + public static void SkippedContentNegotiation(this ILogger logger, string contentType) + { + _skippedContentNegotiation(logger, contentType, null); + } + + public static void NoAcceptForNegotiation(this ILogger logger) + { + _noAcceptForNegotiation(logger, null); + } + + public static void NoFormatterFromNegotiation(this ILogger logger, IList acceptTypes) + { + _noFormatterFromNegotiation(logger, acceptTypes, null); + } + + public static void InputFormatterSelected( + this ILogger logger, + IInputFormatter inputFormatter, + InputFormatterContext formatterContext) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + var contentType = formatterContext.HttpContext.Request.ContentType; + _inputFormatterSelected(logger, inputFormatter, contentType, null); + } + } + + public static void InputFormatterRejected( + this ILogger logger, + IInputFormatter inputFormatter, + InputFormatterContext formatterContext) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + var contentType = formatterContext.HttpContext.Request.ContentType; + _inputFormatterRejected(logger, inputFormatter, contentType, null); + } + } + + public static void NoInputFormatterSelected( + this ILogger logger, + InputFormatterContext formatterContext) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + var contentType = formatterContext.HttpContext.Request.ContentType; + _noInputFormatterSelected(logger, contentType, null); + if (formatterContext.HttpContext.Request.HasFormContentType) + { + var modelType = formatterContext.ModelType.FullName; + var modelName = formatterContext.ModelName; + _removeFromBodyAttribute(logger, modelName, modelType, null); + } + } + } + + public static void RedirectResultExecuting(this ILogger logger, string destination) + { + _redirectResultExecuting(logger, destination, null); + } + + public static void RedirectToActionResultExecuting(this ILogger logger, string destination) + { + _redirectToActionResultExecuting(logger, destination, null); + } + + public static void RedirectToRouteResultExecuting(this ILogger logger, string destination, string routeName) + { + _redirectToRouteResultExecuting(logger, destination, routeName, null); + } + + public static void RedirectToPageResultExecuting(this ILogger logger, string page) + => _redirectToPageResultExecuting(logger, page, null); + + public static void FeatureNotFound(this ILogger logger) + { + _featureNotFound(logger, null); + } + + public static void FeatureIsReadOnly(this ILogger logger) + { + _featureIsReadOnly(logger, null); + } + + public static void MaxRequestBodySizeSet(this ILogger logger, string requestSize) + { + _maxRequestBodySizeSet(logger, requestSize, null); + } + + public static void RequestBodySizeLimitDisabled(this ILogger logger) + { + _requestBodySizeLimitDisabled(logger, null); + } + + public static void CannotApplyRequestFormLimits(this ILogger logger) + { + _cannotApplyRequestFormLimits(logger, null); + } + + public static void AppliedRequestFormLimits(this ILogger logger) + { + _appliedRequestFormLimits(logger, null); + } + + public static void NotMostEffectiveFilter(this ILogger logger, Type overridenFilter, Type overridingFilter, Type policyType) + { + _notMostEffectiveFilter(logger, overridenFilter, overridingFilter, policyType, null); + } + + public static void UnsupportedFormatFilterContentType(this ILogger logger, string format) + { + _unsupportedFormatFilterContentType(logger, format, null); + } + + public static void ActionDoesNotSupportFormatFilterContentType( + this ILogger logger, + string format, + MediaTypeCollection supportedMediaTypes) + { + _actionDoesNotSupportFormatFilterContentType(logger, format, supportedMediaTypes, null); + } + + public static void CannotApplyFormatFilterContentType(this ILogger logger, string format) + { + _cannotApplyFormatFilterContentType(logger, format, null); + } + + public static void ActionDoesNotExplicitlySpecifyContentTypes(this ILogger logger) + { + _actionDoesNotExplicitlySpecifyContentTypes(logger, null); + } + + public static void ModelStateInvalidFilterExecuting(this ILogger logger) => _modelStateInvalidFilterExecuting(logger, null); + + public static void InferredParameterBindingSource( + this ILogger logger, + ParameterModel parameterModel, + BindingSource bindingSource) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + _inferredParameterSource(logger, parameterModel.Action.ActionMethod, parameterModel.ParameterName, bindingSource.DisplayName, null); + } + } + + public static void IfMatchPreconditionFailed(this ILogger logger, EntityTagHeaderValue etag) + { + _ifMatchPreconditionFailed(logger, etag, null); + } + + public static void IfUnmodifiedSincePreconditionFailed( + this ILogger logger, + DateTimeOffset? lastModified, + DateTimeOffset? ifUnmodifiedSinceDate) + { + _ifUnmodifiedSincePreconditionFailed(logger, lastModified, ifUnmodifiedSinceDate, null); + } + + public static void IfRangeLastModifiedPreconditionFailed( + this ILogger logger, + DateTimeOffset? lastModified, + DateTimeOffset? ifRangeLastModifiedDate) + { + _ifRangeLastModifiedPreconditionFailed(logger, lastModified, ifRangeLastModifiedDate, null); + } + + public static void IfRangeETagPreconditionFailed( + this ILogger logger, + EntityTagHeaderValue currentETag, + EntityTagHeaderValue ifRangeTag) + { + _ifRangeETagPreconditionFailed(logger, currentETag, ifRangeTag, null); + } + + public static void RegisteredModelBinderProviders(this ILogger logger, IModelBinderProvider[] providers) + { + _registeredModelBinderProviders(logger, providers, null); + } + + public static void FoundNoValueInRequest(this ILogger logger, ModelBindingContext bindingContext) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var modelMetadata = bindingContext.ModelMetadata; + switch (modelMetadata.MetadataKind) + { + case ModelMetadataKind.Parameter: + _foundNoValueForParameterInRequest( + logger, + bindingContext.ModelName, + modelMetadata.ParameterName, + bindingContext.ModelType, + null); + break; + case ModelMetadataKind.Property: + _foundNoValueForPropertyInRequest( + logger, + bindingContext.ModelName, + modelMetadata.ContainerType, + modelMetadata.PropertyName, + bindingContext.ModelType, + null); + break; + case ModelMetadataKind.Type: + _foundNoValueInRequest( + logger, + bindingContext.ModelName, + bindingContext.ModelType, + null); + break; + } + } + + public static void NoPublicSettableProperties(this ILogger logger, ModelBindingContext bindingContext) + { + _noPublicSettableProperties(logger, bindingContext.ModelName, bindingContext.ModelType, null); + } + + public static void CannotBindToComplexType(this ILogger logger, ModelBindingContext bindingContext) + { + _cannotBindToComplexType(logger, bindingContext.ModelType, null); + } + + public static void CannotBindToFilesCollectionDueToUnsupportedContentType(this ILogger logger, ModelBindingContext bindingContext) + { + _cannotBindToFilesCollectionDueToUnsupportedContentType(logger, bindingContext.ModelName, bindingContext.ModelType, null); + } + + public static void CannotCreateHeaderModelBinderCompatVersion_2_0(this ILogger logger, Type modelType) + { + _cannotCreateHeaderModelBinderCompatVersion_2_0(logger, modelType, null); + } + + public static void CannotCreateHeaderModelBinder(this ILogger logger, Type modelType) + { + _cannotCreateHeaderModelBinder(logger, modelType, null); + } + + public static void NoFilesFoundInRequest(this ILogger logger) + { + _noFilesFoundInRequest(logger, null); + } + + public static void AttemptingToBindModel(this ILogger logger, ModelBindingContext bindingContext) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var modelMetadata = bindingContext.ModelMetadata; + switch (modelMetadata.MetadataKind) + { + case ModelMetadataKind.Parameter: + _attemptingToBindParameterModel( + logger, + modelMetadata.ParameterName, + modelMetadata.ModelType, + bindingContext.ModelName, + null); + break; + case ModelMetadataKind.Property: + _attemptingToBindPropertyModel( + logger, + modelMetadata.ContainerType, + modelMetadata.PropertyName, + modelMetadata.ModelType, + bindingContext.ModelName, + null); + break; + case ModelMetadataKind.Type: + _attemptingToBindModel(logger, bindingContext.ModelType, bindingContext.ModelName, null); + break; + } + } + + public static void DoneAttemptingToBindModel(this ILogger logger, ModelBindingContext bindingContext) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var modelMetadata = bindingContext.ModelMetadata; + switch (modelMetadata.MetadataKind) + { + case ModelMetadataKind.Parameter: + _doneAttemptingToBindParameterModel( + logger, + modelMetadata.ParameterName, + modelMetadata.ModelType, + null); + break; + case ModelMetadataKind.Property: + _doneAttemptingToBindPropertyModel( + logger, + modelMetadata.ContainerType, + modelMetadata.PropertyName, + modelMetadata.ModelType, + null); + break; + case ModelMetadataKind.Type: + _doneAttemptingToBindModel(logger, bindingContext.ModelType, bindingContext.ModelName, null); + break; + } + } + + public static void AttemptingToBindParameterOrProperty( + this ILogger logger, + ParameterDescriptor parameter, + ModelBindingContext bindingContext) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var modelMetadata = bindingContext.ModelMetadata; + switch (modelMetadata.MetadataKind) + { + case ModelMetadataKind.Parameter: + _attemptingToBindParameter(logger, modelMetadata.ParameterName, modelMetadata.ModelType, null); + break; + case ModelMetadataKind.Property: + _attemptingToBindProperty( + logger, + modelMetadata.ContainerType, + modelMetadata.PropertyName, + modelMetadata.ModelType, + null); + break; + case ModelMetadataKind.Type: + if (parameter is ControllerParameterDescriptor parameterDescriptor) + { + _attemptingToBindParameter( + logger, + parameterDescriptor.ParameterInfo.Name, + modelMetadata.ModelType, + null); + } + else + { + // Likely binding a page handler parameter. Due to various special cases, parameter.Name may + // be empty. No way to determine actual name. + _attemptingToBindParameter(logger, parameter.Name, modelMetadata.ModelType, null); + } + break; + } + } + + public static void DoneAttemptingToBindParameterOrProperty( + this ILogger logger, + ParameterDescriptor parameter, + ModelBindingContext bindingContext) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var modelMetadata = bindingContext.ModelMetadata; + switch (modelMetadata.MetadataKind) + { + case ModelMetadataKind.Parameter: + _doneAttemptingToBindParameter(logger, modelMetadata.ParameterName, modelMetadata.ModelType, null); + break; + case ModelMetadataKind.Property: + _doneAttemptingToBindProperty( + logger, + modelMetadata.ContainerType, + modelMetadata.PropertyName, + modelMetadata.ModelType, + null); + break; + case ModelMetadataKind.Type: + if (parameter is ControllerParameterDescriptor parameterDescriptor) + { + _doneAttemptingToBindParameter( + logger, + parameterDescriptor.ParameterInfo.Name, + modelMetadata.ModelType, + null); + } + else + { + // Likely binding a page handler parameter. Due to various special cases, parameter.Name may + // be empty. No way to determine actual name. + _doneAttemptingToBindParameter(logger, parameter.Name, modelMetadata.ModelType, null); + } + break; + } + } + + public static void AttemptingToValidateParameterOrProperty( + this ILogger logger, + ParameterDescriptor parameter, + ModelBindingContext bindingContext) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var modelMetadata = bindingContext.ModelMetadata; + switch (modelMetadata.MetadataKind) + { + case ModelMetadataKind.Parameter: + _attemptingToValidateParameter(logger, modelMetadata.ParameterName, modelMetadata.ModelType, null); + break; + case ModelMetadataKind.Property: + _attemptingToValidateProperty( + logger, + modelMetadata.ContainerType, + modelMetadata.PropertyName, + modelMetadata.ModelType, + null); + break; + case ModelMetadataKind.Type: + if (parameter is ControllerParameterDescriptor parameterDescriptor) + { + _attemptingToValidateParameter( + logger, + parameterDescriptor.ParameterInfo.Name, + modelMetadata.ModelType, + 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. + _attemptingToValidateParameter(logger, parameter.Name, modelMetadata.ModelType, null); + } + break; + } + } + + public static void DoneAttemptingToValidateParameterOrProperty( + this ILogger logger, + ParameterDescriptor parameter, + ModelBindingContext bindingContext) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var modelMetadata = bindingContext.ModelMetadata; + switch (modelMetadata.MetadataKind) + { + case ModelMetadataKind.Parameter: + _doneAttemptingToValidateParameter( + logger, + modelMetadata.ParameterName, + modelMetadata.ModelType, + null); + break; + case ModelMetadataKind.Property: + _doneAttemptingToValidateProperty( + logger, + modelMetadata.ContainerType, + modelMetadata.PropertyName, + modelMetadata.ModelType, + null); + break; + case ModelMetadataKind.Type: + if (parameter is ControllerParameterDescriptor parameterDescriptor) + { + _doneAttemptingToValidateParameter( + logger, + parameterDescriptor.ParameterInfo.Name, + modelMetadata.ModelType, + 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. + _doneAttemptingToValidateParameter(logger, parameter.Name, modelMetadata.ModelType, null); + } + break; + } + } + + public static void NoNonIndexBasedFormatFoundForCollection(this ILogger logger, ModelBindingContext bindingContext) + { + var modelName = bindingContext.ModelName; + _noNonIndexBasedFormatFoundForCollection(logger, modelName, modelName, null); + } + + public static void AttemptingToBindCollectionUsingIndices(this ILogger logger, ModelBindingContext bindingContext) + { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + + var modelName = bindingContext.ModelName; + + var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(bindingContext.ModelType, typeof(IEnumerable<>)); + if (enumerableType != null) + { + var elementType = enumerableType.GenericTypeArguments[0]; + if (elementType.IsGenericType && elementType.GetGenericTypeDefinition().GetTypeInfo() == typeof(KeyValuePair<,>).GetTypeInfo()) + { + _attemptingToBindCollectionOfKeyValuePair(logger, modelName, modelName, modelName, modelName, modelName, modelName, null); + return; + } + } + + _attemptingToBindCollectionUsingIndices(logger, modelName, modelName, modelName, modelName, modelName, modelName, null); + } + + public static void NoKeyValueFormatForDictionaryModelBinder(this ILogger logger, ModelBindingContext bindingContext) + { + _noKeyValueFormatForDictionaryModelBinder( + logger, + bindingContext.ModelName, + bindingContext.ModelName, + bindingContext.ModelName, + null); + } + + private static void LogFilterExecutionPlan( + ILogger logger, + string filterType, + IEnumerable filters) + { + var filterList = _noFilters; + if (filters.Any()) + { + filterList = GetFilterList(filters); + } + + _logFilterExecutionPlan(logger, filterType, filterList, null); + } + + private static string[] GetFilterList(IEnumerable filters) + { + var filterList = new List(); + foreach (var filter in filters) + { + if (filter is IOrderedFilter orderedFilter) + { + filterList.Add($"{filter.GetType()} (Order: {orderedFilter.Order})"); + } + else + { + filterList.Add(filter.GetType().ToString()); + } + } + return filterList.ToArray(); + } + + private class ActionLogScope : IReadOnlyList> + { + private readonly ActionDescriptor _action; + + public ActionLogScope(ActionDescriptor action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + _action = action; + } + + public KeyValuePair this[int index] + { + get + { + if (index == 0) + { + return new KeyValuePair("ActionId", _action.Id); + } + else if (index == 1) + { + return new KeyValuePair("ActionName", _action.DisplayName); + } + throw new IndexOutOfRangeException(nameof(index)); + } + } + + public int Count => 2; + + public IEnumerator> GetEnumerator() + { + for (var i = 0; i < Count; ++i) + { + yield return this[i]; + } + } + + public override string ToString() + { + // We don't include the _action.Id here because it's just an opaque guid, and if + // you have text logging, you can already use the requestId for correlation. + return _action.DisplayName; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs new file mode 100644 index 0000000000..f03e2ca661 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs @@ -0,0 +1,110 @@ +// Copyright (c) .NET 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.Threading; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// Sets up default options for . + /// + public class MvcCoreMvcOptionsSetup : IConfigureOptions + { + private readonly IHttpRequestStreamReaderFactory _readerFactory; + private readonly ILoggerFactory _loggerFactory; + + // Used in tests + public MvcCoreMvcOptionsSetup(IHttpRequestStreamReaderFactory readerFactory) + : this(readerFactory, NullLoggerFactory.Instance) + { + } + + public MvcCoreMvcOptionsSetup(IHttpRequestStreamReaderFactory readerFactory, ILoggerFactory loggerFactory) + { + if (readerFactory == null) + { + throw new ArgumentNullException(nameof(readerFactory)); + } + + _readerFactory = readerFactory; + _loggerFactory = loggerFactory; + } + + public void Configure(MvcOptions options) + { + // Set up ModelBinding + options.ModelBinderProviders.Add(new BinderTypeModelBinderProvider()); + options.ModelBinderProviders.Add(new ServicesModelBinderProvider()); + options.ModelBinderProviders.Add(new BodyModelBinderProvider(options.InputFormatters, _readerFactory, _loggerFactory, options)); + options.ModelBinderProviders.Add(new HeaderModelBinderProvider()); + options.ModelBinderProviders.Add(new FloatingPointTypeModelBinderProvider()); + options.ModelBinderProviders.Add(new EnumTypeModelBinderProvider(options)); + options.ModelBinderProviders.Add(new SimpleTypeModelBinderProvider()); + options.ModelBinderProviders.Add(new CancellationTokenModelBinderProvider()); + options.ModelBinderProviders.Add(new ByteArrayModelBinderProvider()); + options.ModelBinderProviders.Add(new FormFileModelBinderProvider()); + options.ModelBinderProviders.Add(new FormCollectionModelBinderProvider()); + options.ModelBinderProviders.Add(new KeyValuePairModelBinderProvider()); + options.ModelBinderProviders.Add(new DictionaryModelBinderProvider()); + options.ModelBinderProviders.Add(new ArrayModelBinderProvider()); + options.ModelBinderProviders.Add(new CollectionModelBinderProvider()); + options.ModelBinderProviders.Add(new ComplexTypeModelBinderProvider()); + + // Set up filters + options.Filters.Add(new UnsupportedContentTypeFilter()); + + // Set up default output formatters. + options.OutputFormatters.Add(new HttpNoContentOutputFormatter()); + options.OutputFormatters.Add(new StringOutputFormatter()); + options.OutputFormatters.Add(new StreamOutputFormatter()); + + // Set up ValueProviders + options.ValueProviderFactories.Add(new FormValueProviderFactory()); + options.ValueProviderFactories.Add(new RouteValueProviderFactory()); + options.ValueProviderFactories.Add(new QueryStringValueProviderFactory()); + options.ValueProviderFactories.Add(new JQueryFormValueProviderFactory()); + + // Set up metadata providers + ConfigureAdditionalModelMetadataDetailsProvider(options.ModelMetadataDetailsProviders); + + // Set up validators + options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider()); + } + + internal static void ConfigureAdditionalModelMetadataDetailsProvider(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. + modelMetadataDetailsProviders.Add(new ExcludeBindingMetadataProvider(typeof(Type))); + + modelMetadataDetailsProviders.Add(new DefaultBindingMetadataProvider()); + modelMetadataDetailsProviders.Add(new DefaultValidationMetadataProvider()); + + modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(CancellationToken), BindingSource.Special)); + modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFile), BindingSource.FormFile)); + modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormCollection), BindingSource.FormFile)); + modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFileCollection), BindingSource.FormFile)); + + // Add types to be excluded from Validation + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Type))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Uri))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(CancellationToken))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IFormFile))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IFormCollection))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IFormFileCollection))); + modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Stream))); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreRouteOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreRouteOptionsSetup.cs new file mode 100644 index 0000000000..6ed400f506 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreRouteOptionsSetup.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 Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// Sets up MVC default options for . + /// + public class MvcCoreRouteOptionsSetup : IConfigureOptions + { + /// + /// Configures the . + /// + /// The . + public void Configure(RouteOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.ConstraintMap.Add("exists", typeof(KnownRouteValueConstraint)); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcMarkerService.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcMarkerService.cs new file mode 100644 index 0000000000..c2eb80ada2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcMarkerService.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.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A marker class used to determine if all the MVC services were added + /// to the before MVC is configured. + /// + public class MvcMarkerService + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcRouteHandler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcRouteHandler.cs new file mode 100644 index 0000000000..21903703fa --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcRouteHandler.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.Diagnostics; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class MvcRouteHandler : IRouter + { + private readonly IActionContextAccessor _actionContextAccessor; + private readonly IActionInvokerFactory _actionInvokerFactory; + private readonly IActionSelector _actionSelector; + private readonly ILogger _logger; + private readonly DiagnosticSource _diagnosticSource; + + public MvcRouteHandler( + IActionInvokerFactory actionInvokerFactory, + IActionSelector actionSelector, + DiagnosticSource diagnosticSource, + ILoggerFactory loggerFactory) + : this(actionInvokerFactory, actionSelector, diagnosticSource, loggerFactory, actionContextAccessor: null) + { + } + + public MvcRouteHandler( + IActionInvokerFactory actionInvokerFactory, + IActionSelector actionSelector, + DiagnosticSource diagnosticSource, + ILoggerFactory loggerFactory, + IActionContextAccessor actionContextAccessor) + { + // The IActionContextAccessor is optional. We want to avoid the overhead of using CallContext + // if possible. + _actionContextAccessor = actionContextAccessor; + + _actionInvokerFactory = actionInvokerFactory; + _actionSelector = actionSelector; + _diagnosticSource = diagnosticSource; + _logger = loggerFactory.CreateLogger(); + } + + public VirtualPathData GetVirtualPath(VirtualPathContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // We return null here because we're not responsible for generating the url, the route is. + return null; + } + + public Task RouteAsync(RouteContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var candidates = _actionSelector.SelectCandidates(context); + if (candidates == null || candidates.Count == 0) + { + _logger.NoActionsMatched(context.RouteData.Values); + return Task.CompletedTask; + } + + var actionDescriptor = _actionSelector.SelectBestCandidate(context, candidates); + if (actionDescriptor == null) + { + _logger.NoActionsMatched(context.RouteData.Values); + return Task.CompletedTask; + } + + context.Handler = (c) => + { + var routeData = c.GetRouteData(); + + var actionContext = new ActionContext(context.HttpContext, routeData, actionDescriptor); + if (_actionContextAccessor != null) + { + _actionContextAccessor.ActionContext = actionContext; + } + + var invoker = _actionInvokerFactory.CreateInvoker(actionContext); + if (invoker == null) + { + throw new InvalidOperationException( + Resources.FormatActionInvokerFactory_CouldNotCreateInvoker( + actionDescriptor.DisplayName)); + } + + return invoker.InvokeAsync(); + }; + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NoOpBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NoOpBinder.cs new file mode 100644 index 0000000000..0fd7146fc3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NoOpBinder.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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class NoOpBinder : IModelBinder + { + public static readonly IModelBinder Instance = new NoOpBinder(); + + public Task BindModelAsync(ModelBindingContext bindingContext) + { + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NonDisposableStream.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NonDisposableStream.cs new file mode 100644 index 0000000000..9cc9a46ea4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NonDisposableStream.cs @@ -0,0 +1,186 @@ +// Copyright (c) .NET 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.Threading; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// Stream that delegates to an inner stream. + /// This Stream is present so that the inner stream is not closed + /// even when Close() or Dispose() is called. + /// + public class NonDisposableStream : Stream + { + private readonly Stream _innerStream; + + /// + /// Initializes a new . + /// + /// The stream which should not be closed or flushed. + public NonDisposableStream(Stream innerStream) + { + if (innerStream == null) + { + throw new ArgumentNullException(nameof(innerStream)); + } + + _innerStream = innerStream; + } + + /// + /// The inner stream this object delegates to. + /// + protected Stream InnerStream => _innerStream; + + /// + public override bool CanRead => _innerStream.CanRead; + + /// + public override bool CanSeek => _innerStream.CanSeek; + + /// + public override bool CanWrite => _innerStream.CanWrite; + + /// + public override long Length => _innerStream.Length; + /// + public override long Position + { + get { return _innerStream.Position; } + set { _innerStream.Position = value; } + } + + /// + public override int ReadTimeout + { + get { return _innerStream.ReadTimeout; } + set { _innerStream.ReadTimeout = value; } + } + + /// + public override bool CanTimeout => _innerStream.CanTimeout; + + /// + public override int WriteTimeout + { + get { return _innerStream.WriteTimeout; } + set { _innerStream.WriteTimeout = value; } + } + + /// + public override long Seek(long offset, SeekOrigin origin) + { + return _innerStream.Seek(offset, origin); + } + + /// + public override int Read(byte[] buffer, int offset, int count) + { + return _innerStream.Read(buffer, offset, count); + } + + /// + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _innerStream.ReadAsync(buffer, offset, count, cancellationToken); + } + + /// + public override IAsyncResult BeginRead( + byte[] buffer, + int offset, + int count, + AsyncCallback callback, + object state) + { + return _innerStream.BeginRead(buffer, offset, count, callback, state); + } + + /// + public override int EndRead(IAsyncResult asyncResult) + { + return _innerStream.EndRead(asyncResult); + } + + /// + public override IAsyncResult BeginWrite( + byte[] buffer, + int offset, + int count, + AsyncCallback callback, + object state) + { + return _innerStream.BeginWrite(buffer, offset, count, callback, state); + } + + /// + public override void EndWrite(IAsyncResult asyncResult) + { + _innerStream.EndWrite(asyncResult); + } + + /// + public override void Close() + { + } + + /// + public override int ReadByte() + { + return _innerStream.ReadByte(); + } + + /// + public override void Flush() + { + // Do nothing, we want to explicitly avoid flush because it turns on Chunked encoding. + } + + /// + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return _innerStream.CopyToAsync(destination, bufferSize, cancellationToken); + } + + /// + public override Task FlushAsync(CancellationToken cancellationToken) + { + return _innerStream.FlushAsync(cancellationToken); + } + + /// + public override void SetLength(long value) + { + _innerStream.SetLength(value); + } + + /// + public override void Write(byte[] buffer, int offset, int count) + { + _innerStream.Write(buffer, offset, count); + } + + /// + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return _innerStream.WriteAsync(buffer, offset, count, cancellationToken); + } + + /// + public override void WriteByte(byte value) + { + _innerStream.WriteByte(value); + } + + /// + protected override void Dispose(bool disposing) + { + // No-op. In CoreCLR this is equivalent to Close. + // Given that we don't own the underlying stream, we never want to do anything interesting here. + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NormalizedRouteValue.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NormalizedRouteValue.cs new file mode 100644 index 0000000000..2da629c22e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NormalizedRouteValue.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; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public static class NormalizedRouteValue + { + /// + /// Gets the case-normalized route value for the specified route . + /// + /// The . + /// The route key to lookup. + /// The value corresponding to the key. + /// + /// The casing of a route value in is determined by the client. + /// This making constructing paths for view locations in a case sensitive file system unreliable. Using the + /// to get route values + /// produces consistently cased results. + /// + public static string GetNormalizedRouteValue(ActionContext context, string key) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (!context.RouteData.Values.TryGetValue(key, out var routeValue)) + { + return null; + } + + var actionDescriptor = context.ActionDescriptor; + string normalizedValue = null; + + if (actionDescriptor.RouteValues.TryGetValue(key, out var value) && + !string.IsNullOrEmpty(value)) + { + normalizedValue = value; + } + + var stringRouteValue = routeValue?.ToString(); + if (string.Equals(normalizedValue, stringRouteValue, StringComparison.OrdinalIgnoreCase)) + { + return normalizedValue; + } + + return stringRouteValue; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ParameterDefaultValues.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ParameterDefaultValues.cs new file mode 100644 index 0000000000..0d4a641e5f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ParameterDefaultValues.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; +using System.ComponentModel; +using System.Reflection; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public static class ParameterDefaultValues + { + public static object[] GetParameterDefaultValues(MethodInfo methodInfo) + { + if (methodInfo == null) + { + throw new ArgumentNullException(nameof(methodInfo)); + } + + var parameters = methodInfo.GetParameters(); + var values = new object[parameters.Length]; + + for (var i = 0; i < parameters.Length; i++) + { + values[i] = GetParameterDefaultValue(parameters[i]); + } + + return values; + } + + private static object GetParameterDefaultValue(ParameterInfo parameterInfo) + { + if (!ParameterDefaultValue.TryGetDefaultValue(parameterInfo, out var defaultValue)) + { + var defaultValueAttribute = parameterInfo.GetCustomAttribute(inherit: false); + defaultValue = defaultValueAttribute?.Value; + + if (defaultValue == null && parameterInfo.ParameterType.IsValueType) + { + defaultValue = Activator.CreateInstance(parameterInfo.ParameterType); + } + } + return defaultValue; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PlaceholderBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PlaceholderBinder.cs new file mode 100644 index 0000000000..0e6bdc07a1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PlaceholderBinder.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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + // Used as a placeholder to break cycles while building a tree of model binders in ModelBinderFactory. + // + // When a cycle is detected by a call to Create(...), we create an instance of this class and return it + // to break the cycle. Later when the 'real' binder is created we set Inner to point to that. + public class PlaceholderBinder : IModelBinder + { + public IModelBinder Inner { get; set; } + + public Task BindModelAsync(ModelBindingContext bindingContext) + { + return Inner.BindModelAsync(bindingContext); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.cs new file mode 100644 index 0000000000..ade8cc8102 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.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 System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// This is a container for prefix values. It normalizes all the values into dotted-form and then stores + /// them in a sorted array. All queries for prefixes are also normalized to dotted-form, and searches + /// for ContainsPrefix are done with a binary search. + /// + public class PrefixContainer + { + private static readonly char[] Delimiters = new char[] { '[', '.' }; + + private readonly ICollection _originalValues; + private readonly string[] _sortedValues; + + public PrefixContainer(ICollection values) + { + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + _originalValues = values; + + if (_originalValues.Count == 0) + { + _sortedValues = Array.Empty(); + } + else + { + _sortedValues = new string[_originalValues.Count]; + _originalValues.CopyTo(_sortedValues, 0); + Array.Sort(_sortedValues, StringComparer.OrdinalIgnoreCase); + } + } + + public bool ContainsPrefix(string prefix) + { + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + if (_sortedValues.Length == 0) + { + return false; + } + + if (prefix.Length == 0) + { + return true; // Empty prefix matches all elements. + } + + return BinarySearch(prefix) > -1; + } + + // Given "foo.bar", "foo.hello", "something.other", foo[abc].baz and asking for prefix "foo" will return: + // - "bar"/"foo.bar" + // - "hello"/"foo.hello" + // - "abc"/"foo[abc]" + public IDictionary GetKeysFromPrefix(string prefix) + { + var result = new Dictionary(StringComparer.OrdinalIgnoreCase); + + foreach (var entry in _originalValues) + { + if (entry != null) + { + if (entry.Length == prefix.Length) + { + // No key in this entry + continue; + } + + if (prefix.Length == 0) + { + GetKeyFromEmptyPrefix(entry, result); + } + else if (entry.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) + { + GetKeyFromNonEmptyPrefix(prefix, entry, result); + } + } + } + + return result; + } + + private static void GetKeyFromEmptyPrefix(string entry, IDictionary results) + { + string key; + string fullName; + var delimiterPosition = entry.IndexOfAny(Delimiters, 0); + + if (delimiterPosition == 0 && entry[0] == '[') + { + // Handle an entry such as "[key]". + var bracketPosition = entry.IndexOf(']', 1); + if (bracketPosition == -1) + { + // Malformed for dictionary. + return; + } + + key = entry.Substring(1, bracketPosition - 1); + fullName = entry.Substring(0, bracketPosition + 1); + } + else + { + // Handle an entry such as "key", "key.property" and "key[index]". + key = delimiterPosition == -1 ? entry : entry.Substring(0, delimiterPosition); + fullName = key; + } + + if (!results.ContainsKey(key)) + { + results.Add(key, fullName); + } + } + + private static void GetKeyFromNonEmptyPrefix(string prefix, string entry, IDictionary results) + { + string key; + string fullName; + var keyPosition = prefix.Length + 1; + + switch (entry[prefix.Length]) + { + case '.': + // Handle an entry such as "prefix.key", "prefix.key.property" and "prefix.key[index]". + var delimiterPosition = entry.IndexOfAny(Delimiters, keyPosition); + if (delimiterPosition == -1) + { + // Neither '.' nor '[' found later in the name. Use rest of the string. + key = entry.Substring(keyPosition); + fullName = entry; + } + else + { + key = entry.Substring(keyPosition, delimiterPosition - keyPosition); + fullName = entry.Substring(0, delimiterPosition); + } + break; + + case '[': + // Handle an entry such as "prefix[key]". + var bracketPosition = entry.IndexOf(']', keyPosition); + if (bracketPosition == -1) + { + // Malformed for dictionary + return; + } + + key = entry.Substring(keyPosition, bracketPosition - keyPosition); + fullName = entry.Substring(0, bracketPosition + 1); + break; + + default: + // Ignore an entry such as "prefixA". + return; + } + + if (!results.ContainsKey(key)) + { + results.Add(key, fullName); + } + } + + // This is tightly coupled to the definition at ModelStateDictionary.StartsWithPrefix + private int BinarySearch(string prefix) + { + var start = 0; + var end = _sortedValues.Length - 1; + + while (start <= end) + { + var pivot = start + ((end - start) / 2); + var candidate = _sortedValues[pivot]; + var compare = string.Compare( + prefix, + 0, + candidate, + 0, + prefix.Length, + StringComparison.OrdinalIgnoreCase); + if (compare == 0) + { + Debug.Assert(candidate.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); + + // Okay, now we have a candidate that starts with the prefix. If the candidate is longer than + // the prefix, we need to look at the next character and see if it's a delimiter. + if (candidate.Length == prefix.Length) + { + // Exact match + return pivot; + } + + var c = candidate[prefix.Length]; + if (c == '.' || c == '[') + { + // Match, followed by delimiter + return pivot; + } + + // Okay, so the candidate has some extra text. We need to keep searching. + // + // Can often assume the candidate string is greater than the prefix e.g. that works for + // prefix: product + // candidate: productId + // most of the time because "product", "product.id", etc. will sort earlier than "productId". But, + // the assumption isn't correct if "product[0]" is also in _sortedValues because that value will + // sort later than "productId". + // + // Fall back to brute force and cover all the cases. + return LinearSearch(prefix, start, end); + } + + if (compare > 0) + { + start = pivot + 1; + } + else + { + end = pivot - 1; + } + } + + return ~start; + } + + private int LinearSearch(string prefix, int start, int end) + { + for (; start <= end; start++) + { + var candidate = _sortedValues[start]; + var compare = string.Compare( + prefix, + 0, + candidate, + 0, + prefix.Length, + StringComparison.OrdinalIgnoreCase); + if (compare == 0) + { + Debug.Assert(candidate.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)); + + // Okay, now we have a candidate that starts with the prefix. If the candidate is longer than + // the prefix, we need to look at the next character and see if it's a delimiter. + if (candidate.Length == prefix.Length) + { + // Exact match + return start; + } + + var c = candidate[prefix.Length]; + if (c == '.' || c == '[') + { + // Match, followed by delimiter + return start; + } + + // Keep checking until we've passed all StartsWith() matches. + } + + if (compare < 0) + { + // Prefix is less than the candidate. No potential matches left. + break; + } + } + + return ~start; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PropertyValueSetter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PropertyValueSetter.cs new file mode 100644 index 0000000000..a6983106da --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PropertyValueSetter.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 System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public static class PropertyValueSetter + { + private static readonly MethodInfo CallPropertyAddRangeOpenGenericMethod = + typeof(PropertyValueSetter).GetMethod(nameof(CallPropertyAddRange), BindingFlags.NonPublic | BindingFlags.Static); + + public static void SetValue( + ModelMetadata metadata, + object instance, + object value) + { + if (!metadata.IsReadOnly) + { + // Handle settable property. Do not set the property to null if the type is a non-nullable type. + if (value != null || metadata.IsReferenceOrNullableType) + { + metadata.PropertySetter(instance, value); + } + + return; + } + + if (metadata.ModelType.IsArray) + { + // Do not attempt to copy values into an array because an array's length is immutable. This choice + // is also consistent with ComplexTypeModelBinder's handling of a read-only array property. + return; + } + + if (!metadata.IsCollectionType) + { + // Not a collection model. + return; + } + + var target = metadata.PropertyGetter(instance); + if (value == null || target == null) + { + // Nothing to do when source or target is null. + return; + } + + // Handle a read-only collection property. + var propertyAddRange = CallPropertyAddRangeOpenGenericMethod.MakeGenericMethod( + metadata.ElementMetadata.ModelType); + propertyAddRange.Invoke(obj: null, parameters: new[] { target, value }); + } + + // Called via reflection. + private static void CallPropertyAddRange(object target, object source) + { + var targetCollection = (ICollection)target; + var sourceCollection = source as IEnumerable; + if (sourceCollection != null && !targetCollection.IsReadOnly) + { + targetCollection.Clear(); + foreach (var item in sourceCollection) + { + targetCollection.Add(item); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ReferenceEqualityComparer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ReferenceEqualityComparer.cs new file mode 100644 index 0000000000..e2288a01c8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ReferenceEqualityComparer.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 System.Runtime.CompilerServices; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + internal class ReferenceEqualityComparer : IEqualityComparer + { + private static readonly bool IsMono = Type.GetType("Mono.Runtime") != null; + + public static ReferenceEqualityComparer Instance { get; } = new ReferenceEqualityComparer(); + + public new bool Equals(object x, object y) + { + return ReferenceEquals(x, y); + } + + public int GetHashCode(object obj) + { + // RuntimeHelpers.GetHashCode sometimes crashes the runtime on Mono 4.0.4 + // See: https://github.com/aspnet/External/issues/45 + // The workaround here is to just not hash anything, and fall back to an equality check. + if (IsMono) + { + return 0; + } + + return RuntimeHelpers.GetHashCode(obj); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestFormLimitsFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestFormLimitsFilter.cs new file mode 100644 index 0000000000..c60e7e2ea2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestFormLimitsFilter.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; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A filter that configures for the current request. + /// + public class RequestFormLimitsFilter : IAuthorizationFilter, IRequestFormLimitsPolicy + { + private readonly ILogger _logger; + + public RequestFormLimitsFilter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public FormOptions FormOptions { get; set; } + + public void OnAuthorization(AuthorizationFilterContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var effectivePolicy = context.FindEffectivePolicy(); + if (effectivePolicy != null && effectivePolicy != this) + { + _logger.NotMostEffectiveFilter(GetType(), effectivePolicy.GetType(), typeof(IRequestFormLimitsPolicy)); + return; + } + + var features = context.HttpContext.Features; + var formFeature = features.Get(); + + if (formFeature == null || formFeature.Form == null) + { + // Request form has not been read yet, so set the limits + features.Set(new FormFeature(context.HttpContext.Request, FormOptions)); + _logger.AppliedRequestFormLimits(); + } + else + { + _logger.CannotApplyRequestFormLimits(); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitFilter.cs new file mode 100644 index 0000000000..c5653f99a5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitFilter.cs @@ -0,0 +1,66 @@ +// Copyright (c) .NET 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.Features; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// A filter that sets the + /// to the specified . + /// + public class RequestSizeLimitFilter : IAuthorizationFilter, IRequestSizePolicy + { + private readonly ILogger _logger; + + /// + /// Creates a new instance of . + /// + public RequestSizeLimitFilter(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public long Bytes { get; set; } + + /// + /// Sets the to . + /// + /// The . + /// If is not enabled or is read-only, + /// the is not applied. + public void OnAuthorization(AuthorizationFilterContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var effectivePolicy = context.FindEffectivePolicy(); + if (effectivePolicy != null && effectivePolicy != this) + { + _logger.NotMostEffectiveFilter(GetType(), effectivePolicy.GetType(), typeof(IRequestSizePolicy)); + return; + } + + var maxRequestBodySizeFeature = context.HttpContext.Features.Get(); + + if (maxRequestBodySizeFeature == null) + { + _logger.FeatureNotFound(); + } + else if (maxRequestBodySizeFeature.IsReadOnly) + { + _logger.FeatureIsReadOnly(); + } + else + { + maxRequestBodySizeFeature.MaxRequestBodySize = Bytes; + _logger.MaxRequestBodySizeSet(Bytes.ToString()); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResourceInvoker.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResourceInvoker.cs new file mode 100644 index 0000000000..2b73da1c0f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResourceInvoker.cs @@ -0,0 +1,1260 @@ +// Copyright (c) .NET 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.Runtime.ExceptionServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public abstract class ResourceInvoker + { + protected readonly DiagnosticSource _diagnosticSource; + protected readonly ILogger _logger; + protected readonly IActionResultTypeMapper _mapper; + protected readonly ActionContext _actionContext; + protected readonly IFilterMetadata[] _filters; + protected readonly IList _valueProviderFactories; + + private AuthorizationFilterContext _authorizationContext; + private ResourceExecutingContext _resourceExecutingContext; + private ResourceExecutedContext _resourceExecutedContext; + private ExceptionContext _exceptionContext; + private ResultExecutingContext _resultExecutingContext; + private ResultExecutedContext _resultExecutedContext; + + // Do not make this readonly, it's mutable. We don't want to make a copy. + // https://blogs.msdn.microsoft.com/ericlippert/2008/05/14/mutating-readonly-structs/ + protected FilterCursor _cursor; + protected IActionResult _result; + protected object _instance; + + public ResourceInvoker( + DiagnosticSource diagnosticSource, + ILogger logger, + IActionResultTypeMapper mapper, + ActionContext actionContext, + IFilterMetadata[] filters, + IList valueProviderFactories) + { + _diagnosticSource = diagnosticSource ?? throw new ArgumentNullException(nameof(diagnosticSource)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _mapper = mapper ?? throw new ArgumentNullException(nameof(mapper)); + _actionContext = actionContext ?? throw new ArgumentNullException(nameof(actionContext)); + + _filters = filters ?? throw new ArgumentNullException(nameof(filters)); + _valueProviderFactories = valueProviderFactories ?? throw new ArgumentNullException(nameof(valueProviderFactories)); + _cursor = new FilterCursor(filters); + } + + public virtual async Task InvokeAsync() + { + try + { + _diagnosticSource.BeforeAction( + _actionContext.ActionDescriptor, + _actionContext.HttpContext, + _actionContext.RouteData); + + using (_logger.ActionScope(_actionContext.ActionDescriptor)) + { + _logger.ExecutingAction(_actionContext.ActionDescriptor); + + _logger.AuthorizationFiltersExecutionPlan(_filters); + _logger.ResourceFiltersExecutionPlan(_filters); + _logger.ActionFiltersExecutionPlan(_filters); + _logger.ExceptionFiltersExecutionPlan(_filters); + _logger.ResultFiltersExecutionPlan(_filters); + + var stopwatch = ValueStopwatch.StartNew(); + + try + { + await InvokeFilterPipelineAsync(); + } + finally + { + ReleaseResources(); + _logger.ExecutedAction(_actionContext.ActionDescriptor, stopwatch.GetElapsedTime()); + } + } + } + finally + { + _diagnosticSource.AfterAction( + _actionContext.ActionDescriptor, + _actionContext.HttpContext, + _actionContext.RouteData); + } + } + + /// + /// In derived types, releases resources such as controller, model, or page instances created as + /// part of invoking the inner pipeline. + /// + protected abstract void ReleaseResources(); + + private async Task InvokeFilterPipelineAsync() + { + var next = State.InvokeBegin; + + // The `scope` tells the `Next` method who the caller is, and what kind of state to initialize to + // communicate a result. The outermost scope is `Scope.Invoker` and doesn't require any type + // of context or result other than throwing. + var scope = Scope.Invoker; + + // The `state` is used for internal state handling during transitions between states. In practice this + // means storing a filter instance in `state` and then retrieving it in the next state. + var state = (object)null; + + // `isCompleted` will be set to true when we've reached a terminal state. + var isCompleted = false; + + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); + } + } + + protected abstract Task InvokeInnerFilterAsync(); + + protected virtual async Task InvokeResultAsync(IActionResult result) + { + var actionContext = _actionContext; + + _diagnosticSource.BeforeActionResult(actionContext, result); + _logger.BeforeExecutingActionResult(result); + + try + { + await result.ExecuteResultAsync(actionContext); + } + finally + { + _diagnosticSource.AfterActionResult(actionContext, result); + _logger.AfterExecutingActionResult(result); + } + } + + private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) + { + switch (next) + { + case State.InvokeBegin: + { + goto case State.AuthorizationBegin; + } + + case State.AuthorizationBegin: + { + _cursor.Reset(); + goto case State.AuthorizationNext; + } + + case State.AuthorizationNext: + { + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) + { + if (_authorizationContext == null) + { + _authorizationContext = new AuthorizationFilterContext(_actionContext, _filters); + } + + state = current.FilterAsync; + goto case State.AuthorizationAsyncBegin; + } + else if (current.Filter != null) + { + if (_authorizationContext == null) + { + _authorizationContext = new AuthorizationFilterContext(_actionContext, _filters); + } + + state = current.Filter; + goto case State.AuthorizationSync; + } + else + { + goto case State.AuthorizationEnd; + } + } + + case State.AuthorizationAsyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_authorizationContext != null); + + var filter = (IAsyncAuthorizationFilter)state; + var authorizationContext = _authorizationContext; + + _diagnosticSource.BeforeOnAuthorizationAsync(authorizationContext, filter); + _logger.BeforeExecutingMethodOnFilter( + FilterTypeConstants.AuthorizationFilter, + nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync), + filter); + + var task = filter.OnAuthorizationAsync(authorizationContext); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.AuthorizationAsyncEnd; + return task; + } + + goto case State.AuthorizationAsyncEnd; + } + + case State.AuthorizationAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_authorizationContext != null); + + var filter = (IAsyncAuthorizationFilter)state; + var authorizationContext = _authorizationContext; + + _diagnosticSource.AfterOnAuthorizationAsync(authorizationContext, filter); + _logger.AfterExecutingMethodOnFilter( + FilterTypeConstants.AuthorizationFilter, + nameof(IAsyncAuthorizationFilter.OnAuthorizationAsync), + filter); + + if (authorizationContext.Result != null) + { + goto case State.AuthorizationShortCircuit; + } + + goto case State.AuthorizationNext; + } + + case State.AuthorizationSync: + { + Debug.Assert(state != null); + Debug.Assert(_authorizationContext != null); + + var filter = (IAuthorizationFilter)state; + var authorizationContext = _authorizationContext; + + _diagnosticSource.BeforeOnAuthorization(authorizationContext, filter); + _logger.BeforeExecutingMethodOnFilter( + FilterTypeConstants.AuthorizationFilter, + nameof(IAuthorizationFilter.OnAuthorization), + filter); + + filter.OnAuthorization(authorizationContext); + + _diagnosticSource.AfterOnAuthorization(authorizationContext, filter); + _logger.AfterExecutingMethodOnFilter( + FilterTypeConstants.AuthorizationFilter, + nameof(IAuthorizationFilter.OnAuthorization), + filter); + + if (authorizationContext.Result != null) + { + goto case State.AuthorizationShortCircuit; + } + + goto case State.AuthorizationNext; + } + + case State.AuthorizationShortCircuit: + { + Debug.Assert(state != null); + Debug.Assert(_authorizationContext != null); + Debug.Assert(_authorizationContext.Result != null); + + _logger.AuthorizationFailure((IFilterMetadata)state); + + // This is a short-circuit - execute relevant result filters + result and complete this invocation. + isCompleted = true; + _result = _authorizationContext.Result; + return InvokeAlwaysRunResultFilters(); + } + + case State.AuthorizationEnd: + { + goto case State.ResourceBegin; + } + + case State.ResourceBegin: + { + _cursor.Reset(); + goto case State.ResourceNext; + } + + case State.ResourceNext: + { + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) + { + if (_resourceExecutingContext == null) + { + _resourceExecutingContext = new ResourceExecutingContext( + _actionContext, + _filters, + _valueProviderFactories); + } + + state = current.FilterAsync; + goto case State.ResourceAsyncBegin; + } + else if (current.Filter != null) + { + if (_resourceExecutingContext == null) + { + _resourceExecutingContext = new ResourceExecutingContext( + _actionContext, + _filters, + _valueProviderFactories); + } + + state = current.Filter; + goto case State.ResourceSyncBegin; + } + else + { + // All resource filters are currently on the stack - now execute the 'inside'. + goto case State.ResourceInside; + } + } + + case State.ResourceAsyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_resourceExecutingContext != null); + + var filter = (IAsyncResourceFilter)state; + var resourceExecutingContext = _resourceExecutingContext; + + _diagnosticSource.BeforeOnResourceExecution(resourceExecutingContext, filter); + _logger.BeforeExecutingMethodOnFilter( + FilterTypeConstants.ResourceFilter, + nameof(IAsyncResourceFilter.OnResourceExecutionAsync), + filter); + + var task = filter.OnResourceExecutionAsync(resourceExecutingContext, InvokeNextResourceFilterAwaitedAsync); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResourceAsyncEnd; + return task; + } + + goto case State.ResourceAsyncEnd; + } + + case State.ResourceAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_resourceExecutingContext != null); + + var filter = (IAsyncResourceFilter)state; + if (_resourceExecutedContext == null) + { + // If we get here then the filter didn't call 'next' indicating a short circuit. + _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) + { + Canceled = true, + Result = _resourceExecutingContext.Result, + }; + + _diagnosticSource.AfterOnResourceExecution(_resourceExecutedContext, filter); + _logger.AfterExecutingMethodOnFilter( + FilterTypeConstants.ResourceFilter, + nameof(IAsyncResourceFilter.OnResourceExecutionAsync), + filter); + + // A filter could complete a Task without setting a result + if (_resourceExecutingContext.Result != null) + { + goto case State.ResourceShortCircuit; + } + } + + goto case State.ResourceEnd; + } + + case State.ResourceSyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_resourceExecutingContext != null); + + var filter = (IResourceFilter)state; + var resourceExecutingContext = _resourceExecutingContext; + + _diagnosticSource.BeforeOnResourceExecuting(resourceExecutingContext, filter); + _logger.BeforeExecutingMethodOnFilter( + FilterTypeConstants.ResourceFilter, + nameof(IResourceFilter.OnResourceExecuting), + filter); + + filter.OnResourceExecuting(resourceExecutingContext); + + _diagnosticSource.AfterOnResourceExecuting(resourceExecutingContext, filter); + _logger.AfterExecutingMethodOnFilter( + FilterTypeConstants.ResourceFilter, + nameof(IResourceFilter.OnResourceExecuting), + filter); + + if (resourceExecutingContext.Result != null) + { + _resourceExecutedContext = new ResourceExecutedContext(resourceExecutingContext, _filters) + { + Canceled = true, + Result = _resourceExecutingContext.Result, + }; + + goto case State.ResourceShortCircuit; + } + + var task = InvokeNextResourceFilter(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResourceSyncEnd; + return task; + } + + goto case State.ResourceSyncEnd; + } + + case State.ResourceSyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_resourceExecutingContext != null); + Debug.Assert(_resourceExecutedContext != null); + + var filter = (IResourceFilter)state; + var resourceExecutedContext = _resourceExecutedContext; + + _diagnosticSource.BeforeOnResourceExecuted(resourceExecutedContext, filter); + _logger.BeforeExecutingMethodOnFilter( + FilterTypeConstants.ResourceFilter, + nameof(IResourceFilter.OnResourceExecuted), + filter); + + filter.OnResourceExecuted(resourceExecutedContext); + + _diagnosticSource.AfterOnResourceExecuted(resourceExecutedContext, filter); + _logger.AfterExecutingMethodOnFilter( + FilterTypeConstants.ResourceFilter, + nameof(IResourceFilter.OnResourceExecuted), + filter); + + goto case State.ResourceEnd; + } + + case State.ResourceShortCircuit: + { + Debug.Assert(state != null); + Debug.Assert(_resourceExecutingContext != null); + Debug.Assert(_resourceExecutedContext != null); + + _logger.ResourceFilterShortCircuited((IFilterMetadata)state); + + _result = _resourceExecutingContext.Result; + var task = InvokeAlwaysRunResultFilters(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResourceEnd; + return task; + } + + goto case State.ResourceEnd; + } + + case State.ResourceInside: + { + goto case State.ExceptionBegin; + } + + case State.ExceptionBegin: + { + _cursor.Reset(); + goto case State.ExceptionNext; + } + + case State.ExceptionNext: + { + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) + { + state = current.FilterAsync; + goto case State.ExceptionAsyncBegin; + } + else if (current.Filter != null) + { + state = current.Filter; + goto case State.ExceptionSyncBegin; + } + else if (scope == Scope.Exception) + { + // All exception filters are on the stack already - so execute the 'inside'. + goto case State.ExceptionInside; + } + else + { + // There are no exception filters - so jump right to the action. + Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource); + goto case State.ActionBegin; + } + } + + case State.ExceptionAsyncBegin: + { + var task = InvokeNextExceptionFilterAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ExceptionAsyncResume; + return task; + } + + goto case State.ExceptionAsyncResume; + } + + case State.ExceptionAsyncResume: + { + Debug.Assert(state != null); + + var filter = (IAsyncExceptionFilter)state; + var exceptionContext = _exceptionContext; + + // When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception, + // we'll call the filter. Otherwise there's nothing to do. + if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled) + { + _diagnosticSource.BeforeOnExceptionAsync(exceptionContext, filter); + _logger.BeforeExecutingMethodOnFilter( + FilterTypeConstants.ExceptionFilter, + nameof(IAsyncExceptionFilter.OnExceptionAsync), + filter); + + var task = filter.OnExceptionAsync(exceptionContext); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ExceptionAsyncEnd; + return task; + } + + goto case State.ExceptionAsyncEnd; + } + + goto case State.ExceptionEnd; + } + + case State.ExceptionAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_exceptionContext != null); + + var filter = (IAsyncExceptionFilter)state; + var exceptionContext = _exceptionContext; + + _diagnosticSource.AfterOnExceptionAsync(exceptionContext, filter); + _logger.AfterExecutingMethodOnFilter( + FilterTypeConstants.ExceptionFilter, + nameof(IAsyncExceptionFilter.OnExceptionAsync), + filter); + + if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled) + { + // We don't need to do anything to trigger a short circuit. If there's another + // exception filter on the stack it will check the same set of conditions + // and then just skip itself. + _logger.ExceptionFilterShortCircuited(filter); + } + + goto case State.ExceptionEnd; + } + + case State.ExceptionSyncBegin: + { + var task = InvokeNextExceptionFilterAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ExceptionSyncEnd; + return task; + } + + goto case State.ExceptionSyncEnd; + } + + case State.ExceptionSyncEnd: + { + Debug.Assert(state != null); + + var filter = (IExceptionFilter)state; + var exceptionContext = _exceptionContext; + + // When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception, + // we'll call the filter. Otherwise there's nothing to do. + if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled) + { + _diagnosticSource.BeforeOnException(exceptionContext, filter); + _logger.BeforeExecutingMethodOnFilter( + FilterTypeConstants.ExceptionFilter, + nameof(IExceptionFilter.OnException), + filter); + + filter.OnException(exceptionContext); + + _diagnosticSource.AfterOnException(exceptionContext, filter); + _logger.AfterExecutingMethodOnFilter( + FilterTypeConstants.ExceptionFilter, + nameof(IExceptionFilter.OnException), + filter); + + if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled) + { + // We don't need to do anything to trigger a short circuit. If there's another + // exception filter on the stack it will check the same set of conditions + // and then just skip itself. + _logger.ExceptionFilterShortCircuited(filter); + } + } + + goto case State.ExceptionEnd; + } + + case State.ExceptionInside: + { + goto case State.ActionBegin; + } + + case State.ExceptionHandled: + { + // We arrive in this state when an exception happened, but was handled by exception filters + // either by setting ExceptionHandled, or nulling out the Exception or setting a result + // on the ExceptionContext. + // + // We need to execute the result (if any) and then exit gracefully which unwinding Resource + // filters. + + Debug.Assert(state != null); + Debug.Assert(_exceptionContext != null); + + if (_exceptionContext.Result == null) + { + _exceptionContext.Result = new EmptyResult(); + } + + _result = _exceptionContext.Result; + + var task = InvokeAlwaysRunResultFilters(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResourceInsideEnd; + return task; + } + + goto case State.ResourceInsideEnd; + } + + case State.ExceptionEnd: + { + var exceptionContext = _exceptionContext; + + if (scope == Scope.Exception) + { + isCompleted = true; + return Task.CompletedTask; + } + + if (exceptionContext != null) + { + if (exceptionContext.Result != null || + exceptionContext.Exception == null || + exceptionContext.ExceptionHandled) + { + goto case State.ExceptionHandled; + } + + Rethrow(exceptionContext); + Debug.Fail("unreachable"); + } + + var task = InvokeResultFilters(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResourceInsideEnd; + return task; + } + goto case State.ResourceInsideEnd; + } + + case State.ActionBegin: + { + var task = InvokeInnerFilterAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ActionEnd; + return task; + } + + goto case State.ActionEnd; + } + + case State.ActionEnd: + { + if (scope == Scope.Exception) + { + // If we're inside an exception filter, let's allow those filters to 'unwind' before + // the result. + isCompleted = true; + return Task.CompletedTask; + } + + Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource); + var task = InvokeResultFilters(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResourceInsideEnd; + return task; + } + goto case State.ResourceInsideEnd; + } + + case State.ResourceInsideEnd: + { + if (scope == Scope.Resource) + { + _resourceExecutedContext = new ResourceExecutedContext(_actionContext, _filters) + { + Result = _result, + }; + + goto case State.ResourceEnd; + } + + goto case State.InvokeEnd; + } + + case State.ResourceEnd: + { + if (scope == Scope.Resource) + { + isCompleted = true; + return Task.CompletedTask; + } + + Debug.Assert(scope == Scope.Invoker); + Rethrow(_resourceExecutedContext); + + goto case State.InvokeEnd; + } + + case State.InvokeEnd: + { + isCompleted = true; + return Task.CompletedTask; + } + + default: + throw new InvalidOperationException(); + } + } + + private async Task InvokeNextResourceFilterAwaitedAsync() + { + Debug.Assert(_resourceExecutingContext != null); + + if (_resourceExecutingContext.Result != null) + { + // If we get here, it means that an async filter set a result AND called next(). This is forbidden. + var message = Resources.FormatAsyncResourceFilter_InvalidShortCircuit( + typeof(IAsyncResourceFilter).Name, + nameof(ResourceExecutingContext.Result), + typeof(ResourceExecutingContext).Name, + typeof(ResourceExecutionDelegate).Name); + throw new InvalidOperationException(message); + } + + await InvokeNextResourceFilter(); + + Debug.Assert(_resourceExecutedContext != null); + return _resourceExecutedContext; + } + + private async Task InvokeNextResourceFilter() + { + try + { + var scope = Scope.Resource; + var next = State.ResourceNext; + var state = (object)null; + var isCompleted = false; + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); + } + } + catch (Exception exception) + { + _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), + }; + } + + Debug.Assert(_resourceExecutedContext != null); + } + + private async Task InvokeNextExceptionFilterAsync() + { + try + { + var next = State.ExceptionNext; + var state = (object)null; + var scope = Scope.Exception; + var isCompleted = false; + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); + } + } + catch (Exception exception) + { + _exceptionContext = new ExceptionContext(_actionContext, _filters) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), + }; + } + } + + private async Task InvokeAlwaysRunResultFilters() + { + var next = State.ResultBegin; + var scope = Scope.Invoker; + var state = (object)null; + var isCompleted = false; + + while (!isCompleted) + { + await ResultNext(ref next, ref scope, ref state, ref isCompleted); + } + } + + private async Task InvokeResultFilters() + { + var next = State.ResultBegin; + var scope = Scope.Invoker; + var state = (object)null; + var isCompleted = false; + + while (!isCompleted) + { + await ResultNext(ref next, ref scope, ref state, ref isCompleted); + } + } + + private Task ResultNext(ref State next, ref Scope scope, ref object state, ref bool isCompleted) + where TFilter : class, IResultFilter + where TFilterAsync : class, IAsyncResultFilter + { + var resultFilterKind = typeof(TFilter) == typeof(IAlwaysRunResultFilter) ? + FilterTypeConstants.AlwaysRunResultFilter : + FilterTypeConstants.ResultFilter; + + switch (next) + { + case State.ResultBegin: + { + _cursor.Reset(); + goto case State.ResultNext; + } + + case State.ResultNext: + { + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) + { + if (_resultExecutingContext == null) + { + _resultExecutingContext = new ResultExecutingContext(_actionContext, _filters, _result, _instance); + } + + state = current.FilterAsync; + goto case State.ResultAsyncBegin; + } + else if (current.Filter != null) + { + if (_resultExecutingContext == null) + { + _resultExecutingContext = new ResultExecutingContext(_actionContext, _filters, _result, _instance); + } + + state = current.Filter; + goto case State.ResultSyncBegin; + } + else + { + goto case State.ResultInside; + } + } + + case State.ResultAsyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_resultExecutingContext != null); + + var filter = (TFilterAsync)state; + var resultExecutingContext = _resultExecutingContext; + + _diagnosticSource.BeforeOnResultExecution(resultExecutingContext, filter); + _logger.BeforeExecutingMethodOnFilter( + resultFilterKind, + nameof(IAsyncResultFilter.OnResultExecutionAsync), + filter); + + var task = filter.OnResultExecutionAsync(resultExecutingContext, InvokeNextResultFilterAwaitedAsync); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResultAsyncEnd; + return task; + } + + goto case State.ResultAsyncEnd; + } + + case State.ResultAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_resultExecutingContext != null); + + var filter = (TFilterAsync)state; + var resultExecutingContext = _resultExecutingContext; + var resultExecutedContext = _resultExecutedContext; + + if (resultExecutedContext == null || resultExecutingContext.Cancel) + { + // Short-circuited by not calling next || Short-circuited by setting Cancel == true + _logger.ResultFilterShortCircuited(filter); + + _resultExecutedContext = new ResultExecutedContext( + _actionContext, + _filters, + resultExecutingContext.Result, + _instance) + { + Canceled = true, + }; + } + + _diagnosticSource.AfterOnResultExecution(_resultExecutedContext, filter); + _logger.AfterExecutingMethodOnFilter( + resultFilterKind, + nameof(IAsyncResultFilter.OnResultExecutionAsync), + filter); + + goto case State.ResultEnd; + } + + case State.ResultSyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_resultExecutingContext != null); + + var filter = (TFilter)state; + var resultExecutingContext = _resultExecutingContext; + + _diagnosticSource.BeforeOnResultExecuting(resultExecutingContext, filter); + _logger.BeforeExecutingMethodOnFilter( + resultFilterKind, + nameof(IResultFilter.OnResultExecuting), + filter); + + filter.OnResultExecuting(resultExecutingContext); + + _diagnosticSource.AfterOnResultExecuting(resultExecutingContext, filter); + _logger.AfterExecutingMethodOnFilter( + resultFilterKind, + nameof(IResultFilter.OnResultExecuting), + filter); + + if (_resultExecutingContext.Cancel) + { + // Short-circuited by setting Cancel == true + _logger.ResultFilterShortCircuited(filter); + + _resultExecutedContext = new ResultExecutedContext( + resultExecutingContext, + _filters, + resultExecutingContext.Result, + _instance) + { + Canceled = true, + }; + + goto case State.ResultEnd; + } + + var task = InvokeNextResultFilterAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResultSyncEnd; + return task; + } + + goto case State.ResultSyncEnd; + } + + case State.ResultSyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_resultExecutingContext != null); + Debug.Assert(_resultExecutedContext != null); + + var filter = (TFilter)state; + var resultExecutedContext = _resultExecutedContext; + + _diagnosticSource.BeforeOnResultExecuted(resultExecutedContext, filter); + _logger.BeforeExecutingMethodOnFilter( + resultFilterKind, + nameof(IResultFilter.OnResultExecuted), + filter); + + filter.OnResultExecuted(resultExecutedContext); + + _diagnosticSource.AfterOnResultExecuted(resultExecutedContext, filter); + _logger.AfterExecutingMethodOnFilter( + resultFilterKind, + nameof(IResultFilter.OnResultExecuted), + filter); + + goto case State.ResultEnd; + } + + case State.ResultInside: + { + // If we executed result filters then we need to grab the result from there. + if (_resultExecutingContext != null) + { + _result = _resultExecutingContext.Result; + } + + if (_result == null) + { + // The empty result is always flowed back as the 'executed' result if we don't have one. + _result = new EmptyResult(); + } + + var task = InvokeResultAsync(_result); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResultEnd; + return task; + } + + goto case State.ResultEnd; + } + + case State.ResultEnd: + { + var result = _result; + isCompleted = true; + + if (scope == Scope.Result) + { + if (_resultExecutedContext == null) + { + _resultExecutedContext = new ResultExecutedContext(_actionContext, _filters, result, _instance); + } + + return Task.CompletedTask; + } + + Rethrow(_resultExecutedContext); + return Task.CompletedTask; + } + + default: + throw new InvalidOperationException(); // Unreachable. + } + } + + private async Task InvokeNextResultFilterAsync() + where TFilter : class, IResultFilter + where TFilterAsync : class, IAsyncResultFilter + { + try + { + var next = State.ResultNext; + var state = (object)null; + var scope = Scope.Result; + var isCompleted = false; + while (!isCompleted) + { + await ResultNext(ref next, ref scope, ref state, ref isCompleted); + } + } + catch (Exception exception) + { + _resultExecutedContext = new ResultExecutedContext(_actionContext, _filters, _result, _instance) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), + }; + } + + Debug.Assert(_resultExecutedContext != null); + } + + private async Task InvokeNextResultFilterAwaitedAsync() + where TFilter : class, IResultFilter + where TFilterAsync : class, IAsyncResultFilter + { + Debug.Assert(_resultExecutingContext != null); + if (_resultExecutingContext.Cancel) + { + // If we get here, it means that an async filter set cancel == true AND called next(). + // This is forbidden. + var message = Resources.FormatAsyncResultFilter_InvalidShortCircuit( + typeof(IAsyncResultFilter).Name, + nameof(ResultExecutingContext.Cancel), + typeof(ResultExecutingContext).Name, + typeof(ResultExecutionDelegate).Name); + + throw new InvalidOperationException(message); + } + + await InvokeNextResultFilterAsync(); + + Debug.Assert(_resultExecutedContext != null); + return _resultExecutedContext; + } + + private static void Rethrow(ResourceExecutedContext context) + { + if (context == null) + { + return; + } + + if (context.ExceptionHandled) + { + return; + } + + if (context.ExceptionDispatchInfo != null) + { + context.ExceptionDispatchInfo.Throw(); + } + + if (context.Exception != null) + { + throw context.Exception; + } + } + + private static void Rethrow(ExceptionContext context) + { + if (context == null) + { + return; + } + + if (context.ExceptionHandled) + { + return; + } + + if (context.ExceptionDispatchInfo != null) + { + context.ExceptionDispatchInfo.Throw(); + } + + if (context.Exception != null) + { + throw context.Exception; + } + } + + private static void Rethrow(ResultExecutedContext context) + { + if (context == null) + { + return; + } + + if (context.ExceptionHandled) + { + return; + } + + if (context.ExceptionDispatchInfo != null) + { + context.ExceptionDispatchInfo.Throw(); + } + + if (context.Exception != null) + { + throw context.Exception; + } + } + + private enum Scope + { + Invoker, + Resource, + Exception, + Result, + } + + private enum State + { + InvokeBegin, + AuthorizationBegin, + AuthorizationNext, + AuthorizationAsyncBegin, + AuthorizationAsyncEnd, + AuthorizationSync, + AuthorizationShortCircuit, + AuthorizationEnd, + ResourceBegin, + ResourceNext, + ResourceAsyncBegin, + ResourceAsyncEnd, + ResourceSyncBegin, + ResourceSyncEnd, + ResourceShortCircuit, + ResourceInside, + ResourceInsideEnd, + ResourceEnd, + ExceptionBegin, + ExceptionNext, + ExceptionAsyncBegin, + ExceptionAsyncResume, + ExceptionAsyncEnd, + ExceptionSyncBegin, + ExceptionSyncEnd, + ExceptionInside, + ExceptionHandled, + ExceptionEnd, + ActionBegin, + ActionEnd, + ResultBegin, + ResultNext, + ResultAsyncBegin, + ResultAsyncEnd, + ResultSyncBegin, + ResultSyncEnd, + ResultInside, + ResultEnd, + InvokeEnd, + } + + private static class FilterTypeConstants + { + public const string AuthorizationFilter = "Authorization Filter"; + public const string ResourceFilter = "Resource Filter"; + public const string ActionFilter = "Action Filter"; + public const string ExceptionFilter = "Exception Filter"; + public const string ResultFilter = "Result Filter"; + public const string AlwaysRunResultFilter = "Always Run Result Filter"; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilter.cs new file mode 100644 index 0000000000..55d63fc975 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilter.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET 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.Logging; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// An which sets the appropriate headers related to response caching. + /// + public class ResponseCacheFilter : IActionFilter, IResponseCacheFilter + { + private readonly ResponseCacheFilterExecutor _executor; + private readonly ILogger _logger; + + /// + /// Creates a new instance of + /// + /// The profile which contains the settings for + /// . + /// The . + public ResponseCacheFilter(CacheProfile cacheProfile, ILoggerFactory loggerFactory) + { + _executor = new ResponseCacheFilterExecutor(cacheProfile); + _logger = loggerFactory.CreateLogger(GetType()); + } + + /// + /// Gets or sets the duration in seconds for which the response is cached. + /// This is a required parameter. + /// This sets "max-age" in "Cache-control" header. + /// + public int Duration + { + get => _executor.Duration; + set => _executor.Duration = value; + } + + /// + /// Gets or sets the location where the data from a particular URL must be cached. + /// + public ResponseCacheLocation Location + { + get => _executor.Location; + set => _executor.Location = value; + } + + /// + /// Gets or sets the value which determines whether the data should be stored or not. + /// When set to , it sets "Cache-control" header to "no-store". + /// Ignores the "Location" parameter for values other than "None". + /// Ignores the "duration" parameter. + /// + public bool NoStore + { + get => _executor.NoStore; + set => _executor.NoStore = value; + } + + /// + /// Gets or sets the value for the Vary response header. + /// + public string VaryByHeader + { + get => _executor.VaryByHeader; + set => _executor.VaryByHeader = value; + } + + /// + /// Gets or sets the query keys to vary by. + /// + /// + /// requires the response cache middleware. + /// + public string[] VaryByQueryKeys + { + get => _executor.VaryByQueryKeys; + set => _executor.VaryByQueryKeys = value; + } + + /// + public void OnActionExecuting(ActionExecutingContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // If there are more filters which can override the values written by this filter, + // then skip execution of this filter. + var effectivePolicy = context.FindEffectivePolicy(); + if (effectivePolicy != null && effectivePolicy != this) + { + _logger.NotMostEffectiveFilter(GetType(), effectivePolicy.GetType(), typeof(IResponseCacheFilter)); + return; + } + + _executor.Execute(context); + } + + /// + public void OnActionExecuted(ActionExecutedContext context) + { + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilterExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilterExecutor.cs new file mode 100644 index 0000000000..02100b838e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilterExecutor.cs @@ -0,0 +1,134 @@ +// Copyright (c) .NET 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.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.ResponseCaching; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ResponseCacheFilterExecutor + { + private readonly CacheProfile _cacheProfile; + private int? _cacheDuration; + private ResponseCacheLocation? _cacheLocation; + private bool? _cacheNoStore; + private string _cacheVaryByHeader; + private string[] _cacheVaryByQueryKeys; + + public ResponseCacheFilterExecutor(CacheProfile cacheProfile) + { + _cacheProfile = cacheProfile ?? throw new ArgumentNullException(nameof(cacheProfile)); + } + + public int Duration + { + get => _cacheDuration ?? _cacheProfile.Duration ?? 0; + set => _cacheDuration = value; + } + + public ResponseCacheLocation Location + { + get => _cacheLocation ?? _cacheProfile.Location ?? ResponseCacheLocation.Any; + set => _cacheLocation = value; + } + + public bool NoStore + { + get => _cacheNoStore ?? _cacheProfile.NoStore ?? false; + set => _cacheNoStore = value; + } + + public string VaryByHeader + { + get => _cacheVaryByHeader ?? _cacheProfile.VaryByHeader; + set => _cacheVaryByHeader = value; + } + + public string[] VaryByQueryKeys + { + get => _cacheVaryByQueryKeys ?? _cacheProfile.VaryByQueryKeys; + set => _cacheVaryByQueryKeys = value; + } + + public void Execute(FilterContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (!NoStore) + { + // Duration MUST be set (either in the cache profile or in this filter) unless NoStore is true. + if (_cacheProfile.Duration == null && _cacheDuration == null) + { + throw new InvalidOperationException( + Resources.FormatResponseCache_SpecifyDuration(nameof(NoStore), nameof(Duration))); + } + } + + var headers = context.HttpContext.Response.Headers; + + // Clear all headers + headers.Remove(HeaderNames.Vary); + headers.Remove(HeaderNames.CacheControl); + headers.Remove(HeaderNames.Pragma); + + if (!string.IsNullOrEmpty(VaryByHeader)) + { + headers[HeaderNames.Vary] = VaryByHeader; + } + + if (VaryByQueryKeys != null) + { + var responseCachingFeature = context.HttpContext.Features.Get(); + if (responseCachingFeature == null) + { + throw new InvalidOperationException( + Resources.FormatVaryByQueryKeys_Requires_ResponseCachingMiddleware(nameof(VaryByQueryKeys))); + } + responseCachingFeature.VaryByQueryKeys = VaryByQueryKeys; + } + + if (NoStore) + { + headers[HeaderNames.CacheControl] = "no-store"; + + // Cache-control: no-store, no-cache is valid. + if (Location == ResponseCacheLocation.None) + { + headers.AppendCommaSeparatedValues(HeaderNames.CacheControl, "no-cache"); + headers[HeaderNames.Pragma] = "no-cache"; + } + } + else + { + string cacheControlValue; + switch (Location) + { + case ResponseCacheLocation.Any: + cacheControlValue = "public,"; + break; + case ResponseCacheLocation.Client: + cacheControlValue = "private,"; + break; + case ResponseCacheLocation.None: + cacheControlValue = "no-cache,"; + headers[HeaderNames.Pragma] = "no-cache"; + break; + default: + cacheControlValue = null; + break; + } + + cacheControlValue = $"{cacheControlValue}max-age={Duration}"; + headers[HeaderNames.CacheControl] = cacheControlValue; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseContentTypeHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseContentTypeHelper.cs new file mode 100644 index 0000000000..936ef92973 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseContentTypeHelper.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.Diagnostics; +using System.Text; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public static class ResponseContentTypeHelper + { + /// + /// Gets the content type and encoding that need to be used for the response. + /// The priority for selecting the content type is: + /// 1. ContentType property set on the action result + /// 2. property set on + /// 3. Default content type set on the action result + /// + /// + /// The user supplied content type is not modified and is used as is. For example, if user + /// sets the content type to be "text/plain" without any encoding, then the default content type's + /// encoding is used to write the response and the ContentType header is set to be "text/plain" without any + /// "charset" information. + /// + /// ContentType set on the action result + /// property set + /// on + /// The default content type of the action result. + /// The content type to be used for the response content type header + /// Encoding to be used for writing the response + public static void ResolveContentTypeAndEncoding( + string actionResultContentType, + string httpResponseContentType, + string defaultContentType, + out string resolvedContentType, + out Encoding resolvedContentTypeEncoding) + { + Debug.Assert(defaultContentType != null); + + var defaultContentTypeEncoding = MediaType.GetEncoding(defaultContentType); + Debug.Assert(defaultContentTypeEncoding != null); + + // 1. User sets the ContentType property on the action result + if (actionResultContentType != null) + { + resolvedContentType = actionResultContentType; + var actionResultEncoding = MediaType.GetEncoding(actionResultContentType); + resolvedContentTypeEncoding = actionResultEncoding ?? defaultContentTypeEncoding; + return; + } + + // 2. User sets the ContentType property on the http response directly + if (!string.IsNullOrEmpty(httpResponseContentType)) + { + var mediaTypeEncoding = MediaType.GetEncoding(httpResponseContentType); + if (mediaTypeEncoding != null) + { + resolvedContentType = httpResponseContentType; + resolvedContentTypeEncoding = mediaTypeEncoding; + } + else + { + resolvedContentType = httpResponseContentType; + resolvedContentTypeEncoding = defaultContentTypeEncoding; + } + + return; + } + + // 3. Fall-back to the default content type + resolvedContentType = defaultContentType; + resolvedContentTypeEncoding = defaultContentTypeEncoding; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ShortFormDictionaryValidationStrategy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ShortFormDictionaryValidationStrategy.cs new file mode 100644 index 0000000000..31d1db97bf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ShortFormDictionaryValidationStrategy.cs @@ -0,0 +1,121 @@ +// Copyright (c) .NET 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 Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// An implementation of for a dictionary bound with 'short form' style keys. + /// + /// The of the keys of the model dictionary. + /// The of the values of the model dictionary. + /// + /// This implementation handles cases like: + /// + /// Model: IDictionary<string, Student> + /// Query String: ?students[Joey].Age=8&students[Katherine].Age=9 + /// + /// In this case, 'Joey' and 'Katherine' are the keys of the dictionary, used to bind two 'Student' + /// objects. The enumerator returned from this class will yield two 'Student' objects with corresponding + /// keys 'students[Joey]' and 'students[Katherine]' + /// + /// + /// Using this key format, the enumerator enumerates model objects of type . The + /// keys of the dictionary are not validated as they must be simple types. + /// + public class ShortFormDictionaryValidationStrategy : IValidationStrategy + { + private readonly ModelMetadata _valueMetadata; + + /// + /// Creates a new . + /// + /// + /// The mapping from key to dictionary key. + /// + /// + /// The associated with . + /// + public ShortFormDictionaryValidationStrategy( + IEnumerable> keyMappings, + ModelMetadata valueMetadata) + { + KeyMappings = keyMappings; + _valueMetadata = valueMetadata; + } + + /// + /// Gets the mapping from key to dictionary key. + /// + public IEnumerable> KeyMappings { get; } + + /// + public IEnumerator GetChildren( + ModelMetadata metadata, + string key, + object model) + { + // key is not needed because KeyMappings maps from full ModelState keys to dictionary keys. + return new Enumerator(_valueMetadata, KeyMappings, (IDictionary)model); + } + + private class Enumerator : IEnumerator + { + private readonly ModelMetadata _metadata; + private readonly IDictionary _model; + private readonly IEnumerator> _keyMappingEnumerator; + + private ValidationEntry _entry; + + public Enumerator( + ModelMetadata metadata, + IEnumerable> keyMappings, + IDictionary model) + { + _metadata = metadata; + _model = model; + _keyMappingEnumerator = keyMappings.GetEnumerator(); + } + + public ValidationEntry Current => _entry; + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + TValue value; + while (true) + { + if (!_keyMappingEnumerator.MoveNext()) + { + return false; + } + + if (_model.TryGetValue(_keyMappingEnumerator.Current.Value, out value)) + { + // Skip over entries that we can't find in the dictionary, they will show up as unvalidated. + break; + } + } + + _entry = new ValidationEntry(_metadata, _keyMappingEnumerator.Current.Key, value); + + return true; + } + + public void Dispose() + { + } + + public void Reset() + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/TypeActivatorCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/TypeActivatorCache.cs new file mode 100644 index 0000000000..36c0d947d3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/TypeActivatorCache.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.Collections.Concurrent; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + /// + /// Caches instances produced by + /// . + /// + public class TypeActivatorCache : ITypeActivatorCache + { + private readonly Func _createFactory = + (type) => ActivatorUtilities.CreateFactory(type, Type.EmptyTypes); + private readonly ConcurrentDictionary _typeActivatorCache = + new ConcurrentDictionary(); + + /// + public TInstance CreateInstance( + IServiceProvider serviceProvider, + Type implementationType) + { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + if (implementationType == null) + { + throw new ArgumentNullException(nameof(implementationType)); + } + + var createFactory = _typeActivatorCache.GetOrAdd(implementationType, _createFactory); + return (TInstance)createFactory(serviceProvider, arguments: null); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs new file mode 100644 index 0000000000..61fcfe0c15 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs @@ -0,0 +1,144 @@ +// Copyright (c) .NET 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.Collections.Generic; +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ValidatorCache + { + private readonly ConcurrentDictionary _cacheEntries = new ConcurrentDictionary(); + + public IReadOnlyList GetValidators(ModelMetadata metadata, IModelValidatorProvider validatorProvider) + { + CacheEntry entry; + if (_cacheEntries.TryGetValue(metadata, out entry)) + { + return GetValidatorsFromEntry(entry, metadata, validatorProvider); + } + + var items = new List(metadata.ValidatorMetadata.Count); + for (var i = 0; i < metadata.ValidatorMetadata.Count; i++) + { + items.Add(new ValidatorItem(metadata.ValidatorMetadata[i])); + } + + ExecuteProvider(validatorProvider, metadata, items); + + var validators = ExtractValidators(items); + + var allValidatorsCached = true; + for (var i = 0; i < items.Count; i++) + { + var item = items[i]; + if (!item.IsReusable) + { + item.Validator = null; + allValidatorsCached = false; + } + } + + if (allValidatorsCached) + { + entry = new CacheEntry(validators); + } + else + { + entry = new CacheEntry(items); + } + + _cacheEntries.TryAdd(metadata, entry); + + return validators; + } + + private IReadOnlyList GetValidatorsFromEntry(CacheEntry entry, ModelMetadata metadata, IModelValidatorProvider validationProvider) + { + Debug.Assert(entry.Validators != null || entry.Items != null); + + if (entry.Validators != null) + { + return entry.Validators; + } + + var items = new List(entry.Items.Count); + for (var i = 0; i < entry.Items.Count; i++) + { + var item = entry.Items[i]; + if (item.IsReusable) + { + items.Add(item); + } + else + { + items.Add(new ValidatorItem(item.ValidatorMetadata)); + } + } + + ExecuteProvider(validationProvider, metadata, items); + + return ExtractValidators(items); + } + + private void ExecuteProvider(IModelValidatorProvider validatorProvider, ModelMetadata metadata, List items) + { + var context = new ModelValidatorProviderContext(metadata, items); + validatorProvider.CreateValidators(context); + } + + private IReadOnlyList ExtractValidators(List items) + { + var count = 0; + for (var i = 0; i < items.Count; i++) + { + if (items[i].Validator != null) + { + count++; + } + } + + if (count == 0) + { + return Array.Empty(); + } + + var validators = new IModelValidator[count]; + + var validatorIndex = 0; + for (int i = 0; i < items.Count; i++) + { + var validator = items[i].Validator; + if (validator != null) + { + validators[validatorIndex++] = validator; + } + } + + return validators; + } + + private struct CacheEntry + { + public CacheEntry(IReadOnlyList validators) + { + Validators = validators; + Items = null; + } + + public CacheEntry(List items) + { + Items = items; + Validators = null; + } + + public IReadOnlyList Validators { get; } + + public List Items { get; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ViewEnginePath.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ViewEnginePath.cs new file mode 100644 index 0000000000..29d4f85597 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ViewEnginePath.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; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public static class ViewEnginePath + { + public static readonly char[] PathSeparators = new[] { '/', '\\' }; + private const string CurrentDirectoryToken = "."; + private const string ParentDirectoryToken = ".."; + + public static string CombinePath(string first, string second) + { + Debug.Assert(!string.IsNullOrEmpty(first)); + + if (second.StartsWith("/", StringComparison.Ordinal)) + { + // "second" is already an app-rooted path. Return it as-is. + return second; + } + + string result; + + // Get directory name (including final slash) but do not use Path.GetDirectoryName() to preserve path + // normalization. + var index = first.LastIndexOf('/'); + Debug.Assert(index >= 0); + + if (index == first.Length - 1) + { + // If the first ends in a trailing slash e.g. "/Home/", assume it's a directory. + result = first + second; + } + else + { + result = first.Substring(0, index + 1) + second; + } + + return ResolvePath(result); + } + + public static string ResolvePath(string path) + { + Debug.Assert(!string.IsNullOrEmpty(path)); + var pathSegment = new StringSegment(path); + if (path[0] == PathSeparators[0] || path[0] == PathSeparators[1]) + { + // Leading slashes (e.g. "/Views/Index.cshtml") always generate an empty first token. Ignore these + // for purposes of resolution. + pathSegment = pathSegment.Subsegment(1); + } + + var tokenizer = new StringTokenizer(pathSegment, PathSeparators); + var requiresResolution = false; + foreach (var segment in tokenizer) + { + // Determine if we need to do any path resolution. + // We need to resolve paths with multiple path separators (e.g "//" or "\\") or, directory traversals e.g. ("../" or "./"). + if (segment.Length == 0 || + segment.Equals(ParentDirectoryToken, StringComparison.Ordinal) || + segment.Equals(CurrentDirectoryToken, StringComparison.Ordinal)) + { + requiresResolution = true; + break; + } + } + + if (!requiresResolution) + { + return path; + } + + var pathSegments = new List(); + foreach (var segment in tokenizer) + { + if (segment.Length == 0) + { + // Ignore multiple directory separators + continue; + } + if (segment.Equals(ParentDirectoryToken, StringComparison.Ordinal)) + { + if (pathSegments.Count == 0) + { + // Don't resolve the path if we ever escape the file system root. We can't reason about it in a + // consistent way. + return path; + } + pathSegments.RemoveAt(pathSegments.Count - 1); + } + else if (segment.Equals(CurrentDirectoryToken, StringComparison.Ordinal)) + { + // We already have the current directory + continue; + } + else + { + pathSegments.Add(segment); + } + } + + var builder = new StringBuilder(); + for (var i = 0; i < pathSegments.Count; i++) + { + var segment = pathSegments[i]; + builder.Append('/'); + builder.Append(segment.Buffer, segment.Offset, segment.Length); + } + + return builder.ToString(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/LocalRedirectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/LocalRedirectResult.cs new file mode 100644 index 0000000000..ca20eb5020 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/LocalRedirectResult.cs @@ -0,0 +1,146 @@ +// Copyright (c) .NET 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.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that returns a Found (302), Moved Permanently (301), Temporary Redirect (307), + /// or Permanent Redirect (308) response with a Location header to the supplied local URL. + /// + public class LocalRedirectResult : ActionResult + { + private string _localUrl; + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The local URL to redirect to. + public LocalRedirectResult(string localUrl) + : this(localUrl, permanent: false) + { + } + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The local URL to redirect to. + /// Specifies whether the redirect should be permanent (301) or temporary (302). + public LocalRedirectResult(string localUrl, bool permanent) + : this(localUrl, permanent, preserveMethod: false) + { + } + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The local URL to redirect to. + /// Specifies whether the redirect should be permanent (301) or temporary (302). + /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request's method. + public LocalRedirectResult(string localUrl, bool permanent, bool preserveMethod) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + Permanent = permanent; + PreserveMethod = preserveMethod; + Url = localUrl; + } + + /// + /// Gets or sets the value that specifies that the redirect should be permanent if true or temporary if false. + /// + public bool Permanent { get; set; } + + /// + /// Gets or sets an indication that the redirect preserves the initial request method. + /// + public bool PreserveMethod { get; set; } + + /// + /// Gets or sets the local URL to redirect to. + /// + public string Url + { + get => _localUrl; + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(value)); + } + + _localUrl = value; + } + } + + /// + /// Gets or sets the for this result. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + public override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var executor = context.HttpContext.RequestServices.GetRequiredService>(); + return executor.ExecuteAsync(context, this); + } + +#pragma warning disable CS0809 + [Obsolete("This implementation will be removed in a future release, use ExecuteResultAsync.")] + public override void ExecuteResult(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var services = context.HttpContext.RequestServices; + var urlHelperFactory = services.GetRequiredService(); + var logger = services.GetRequiredService>(); + + var urlHelper = UrlHelper ?? urlHelperFactory.GetUrlHelper(context); + + // IsLocalUrl is called to handle Urls starting with '~/'. + if (!urlHelper.IsLocalUrl(Url)) + { + throw new InvalidOperationException(Resources.UrlNotLocal); + } + + var destinationUrl = urlHelper.Content(Url); + logger.LocalRedirectResultExecuting(destinationUrl); + + if (PreserveMethod) + { + context.HttpContext.Response.StatusCode = Permanent ? + StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect; + context.HttpContext.Response.Headers[HeaderNames.Location] = destinationUrl; + } + else + { + context.HttpContext.Response.Redirect(destinationUrl, Permanent); + } + } +#pragma warning restore CS0809 + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj new file mode 100644 index 0000000000..8014ae9cc2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj @@ -0,0 +1,47 @@ + + + + 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. +Commonly used types: +Microsoft.AspNetCore.Mvc.AreaAttribute +Microsoft.AspNetCore.Mvc.BindAttribute +Microsoft.AspNetCore.Mvc.ControllerBase +Microsoft.AspNetCore.Mvc.FromBodyAttribute +Microsoft.AspNetCore.Mvc.FromFormAttribute +Microsoft.AspNetCore.Mvc.RequireHttpsAttribute +Microsoft.AspNetCore.Mvc.RouteAttribute + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;aspnetcoremvc + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinderAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinderAttribute.cs new file mode 100644 index 0000000000..c80e2d0fe5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinderAttribute.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 Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An attribute that can specify a model name or type of to use for binding. + /// + [AttributeUsage( + + // Support method parameters in actions. + AttributeTargets.Parameter | + + // Support properties on model DTOs. + AttributeTargets.Property | + + // Support model types. + AttributeTargets.Class | + AttributeTargets.Enum | + AttributeTargets.Struct, + + AllowMultiple = false, + Inherited = true)] + public class ModelBinderAttribute : Attribute, IModelNameProvider, IBinderTypeProviderMetadata + { + private BindingSource _bindingSource; + + /// + /// Initializes a new instance of . + /// + public ModelBinderAttribute() + { + } + + /// + /// Initializes a new instance of . + /// + /// A which implements . + public ModelBinderAttribute(Type binderType) + { + if (binderType == null) + { + throw new ArgumentNullException(nameof(binderType)); + } + BinderType = binderType; + } + + /// + public Type BinderType { get; set; } + + /// + public virtual BindingSource BindingSource + { + get + { + if (_bindingSource == null && BinderType != null) + { + return BindingSource.Custom; + } + + return _bindingSource; + } + protected set + { + _bindingSource = value; + } + } + + /// + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindNeverAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindNeverAttribute.cs new file mode 100644 index 0000000000..12a5077d41 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindNeverAttribute.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 Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Indicates that a property should be excluded from model binding. When applied to a property, the model binding + /// system excludes that property. When applied to a type, the model binding system excludes all properties that + /// type defines. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public sealed class BindNeverAttribute : BindingBehaviorAttribute + { + /// + /// Initializes a new instance. + /// + public BindNeverAttribute() + : base(BindingBehavior.Never) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindRequiredAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindRequiredAttribute.cs new file mode 100644 index 0000000000..247dde0e26 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindRequiredAttribute.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 Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Indicates that a property is required for model binding. When applied to a property, the model binding system + /// requires a value for that property. When applied to a type, the model binding system requires values for all + /// properties that type defines. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public sealed class BindRequiredAttribute : BindingBehaviorAttribute + { + /// + /// Initializes a new instance. + /// + public BindRequiredAttribute() + : base(BindingBehavior.Required) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinder.cs new file mode 100644 index 0000000000..96c6a31288 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinder.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET 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.Linq; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// implementation for binding array values. + /// + /// Type of elements in the array. + public class ArrayModelBinder : CollectionModelBinder + { + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that also takes an . + /// Creates a new . + /// + /// + /// The for binding . + /// + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that also takes an " + nameof(ILoggerFactory) + ".")] + public ArrayModelBinder(IModelBinder elementBinder) + : this(elementBinder, NullLoggerFactory.Instance) + { + } + + /// + /// Creates a new . + /// + /// + /// The for binding . + /// + /// The . + public ArrayModelBinder(IModelBinder elementBinder, ILoggerFactory loggerFactory) + : base(elementBinder, loggerFactory) + { + } + + /// + public override bool CanCreateInstance(Type targetType) + { + return targetType == typeof(TElement[]); + } + + /// + protected override object CreateEmptyCollection(Type targetType) + { + Debug.Assert(targetType == typeof(TElement[]), "GenericModelBinder only creates this binder for arrays."); + + return Array.Empty(); + } + + /// + protected override object ConvertToCollectionType(Type targetType, IEnumerable collection) + { + Debug.Assert(targetType == typeof(TElement[]), "GenericModelBinder only creates this binder for arrays."); + + // If non-null, collection is a List, never already a TElement[]. + return collection?.ToArray(); + } + + /// + protected override void CopyToModel(object target, IEnumerable sourceCollection) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + // Do not attempt to copy values into an array because an array's length is immutable. This choice is also + // consistent with our handling of a read-only array property. + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinderProvider.cs new file mode 100644 index 0000000000..190390b9c7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinderProvider.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.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for arrays. + /// + public class ArrayModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.Metadata.ModelType.IsArray) + { + var elementType = context.Metadata.ElementMetadata.ModelType; + var elementBinder = context.CreateBinder(context.Metadata.ElementMetadata); + + var binderType = typeof(ArrayModelBinder<>).MakeGenericType(elementType); + var loggerFactory = context.Services.GetRequiredService(); + return (IModelBinder)Activator.CreateInstance(binderType, elementBinder, loggerFactory); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinder.cs new file mode 100644 index 0000000000..6f715ccdd0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinder.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 System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for models which specify an using + /// . + /// + public class BinderTypeModelBinder : IModelBinder + { + private readonly ObjectFactory _factory; + + /// + /// Creates a new . + /// + /// The of the . + public BinderTypeModelBinder(Type binderType) + { + if (binderType == null) + { + throw new ArgumentNullException(nameof(binderType)); + } + + if (!typeof(IModelBinder).GetTypeInfo().IsAssignableFrom(binderType.GetTypeInfo())) + { + throw new ArgumentException( + Resources.FormatBinderType_MustBeIModelBinder( + binderType.FullName, + typeof(IModelBinder).FullName), + nameof(binderType)); + } + + _factory = ActivatorUtilities.CreateFactory(binderType, Type.EmptyTypes); + } + + /// + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + var requestServices = bindingContext.HttpContext.RequestServices; + var binder = (IModelBinder)_factory(requestServices, arguments: null); + + await binder.BindModelAsync(bindingContext); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinderProvider.cs new file mode 100644 index 0000000000..aa56c0edcb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinderProvider.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for models which specify an + /// using . + /// + public class BinderTypeModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.BindingInfo.BinderType != null) + { + return new BinderTypeModelBinder(context.BindingInfo.BinderType); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinder.cs new file mode 100644 index 0000000000..8e0744cfda --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinder.cs @@ -0,0 +1,207 @@ +// Copyright (c) .NET 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.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An which binds models from the request body using an + /// when a model has the binding source . + /// + public class BodyModelBinder : IModelBinder + { + private readonly IList _formatters; + private readonly Func _readerFactory; + private readonly ILogger _logger; + private readonly MvcOptions _options; + + /// + /// Creates a new . + /// + /// The list of . + /// + /// The , used to create + /// instances for reading the request body. + /// + public BodyModelBinder(IList formatters, IHttpRequestStreamReaderFactory readerFactory) + : this(formatters, readerFactory, loggerFactory: null) + { + } + + /// + /// Creates a new . + /// + /// The list of . + /// + /// The , used to create + /// instances for reading the request body. + /// + /// The . + public BodyModelBinder( + IList formatters, + IHttpRequestStreamReaderFactory readerFactory, + ILoggerFactory loggerFactory) + : this(formatters, readerFactory, loggerFactory, options: null) + { + } + + /// + /// Creates a new . + /// + /// The list of . + /// + /// The , used to create + /// instances for reading the request body. + /// + /// The . + /// The . + public BodyModelBinder( + IList formatters, + IHttpRequestStreamReaderFactory readerFactory, + ILoggerFactory loggerFactory, + MvcOptions options) + { + if (formatters == null) + { + throw new ArgumentNullException(nameof(formatters)); + } + + if (readerFactory == null) + { + throw new ArgumentNullException(nameof(readerFactory)); + } + + _formatters = formatters; + _readerFactory = readerFactory.CreateReader; + + if (loggerFactory != null) + { + _logger = loggerFactory.CreateLogger(); + } + + _options = options; + } + + /// + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + _logger?.AttemptingToBindModel(bindingContext); + + // Special logic for body, treat the model name as string.Empty for the top level + // object, but allow an override via BinderModelName. The purpose of this is to try + // and be similar to the behavior for POCOs bound via traditional model binding. + string modelBindingKey; + if (bindingContext.IsTopLevelObject) + { + modelBindingKey = bindingContext.BinderModelName ?? string.Empty; + } + else + { + modelBindingKey = bindingContext.ModelName; + } + + var httpContext = bindingContext.HttpContext; + + var allowEmptyInputInModelBinding = _options?.AllowEmptyInputInBodyModelBinding == true; + + var formatterContext = new InputFormatterContext( + httpContext, + modelBindingKey, + bindingContext.ModelState, + bindingContext.ModelMetadata, + _readerFactory, + allowEmptyInputInModelBinding); + + var formatter = (IInputFormatter)null; + for (var i = 0; i < _formatters.Count; i++) + { + if (_formatters[i].CanRead(formatterContext)) + { + formatter = _formatters[i]; + _logger?.InputFormatterSelected(formatter, formatterContext); + break; + } + else + { + _logger?.InputFormatterRejected(_formatters[i], formatterContext); + } + } + + if (formatter == null) + { + _logger?.NoInputFormatterSelected(formatterContext); + + var message = Resources.FormatUnsupportedContentType(httpContext.Request.ContentType); + var exception = new UnsupportedContentTypeException(message); + bindingContext.ModelState.AddModelError(modelBindingKey, exception, bindingContext.ModelMetadata); + _logger?.DoneAttemptingToBindModel(bindingContext); + return; + } + + try + { + var result = await formatter.ReadAsync(formatterContext); + + if (result.HasError) + { + // Formatter encountered an error. Do not use the model it returned. + _logger?.DoneAttemptingToBindModel(bindingContext); + return; + } + + if (result.IsModelSet) + { + var model = result.Model; + bindingContext.Result = ModelBindingResult.Success(model); + } + else + { + // If the input formatter gives a "no value" result, that's always a model state error, + // because BodyModelBinder implicitly regards input as being required for model binding. + // If instead the input formatter wants to treat the input as optional, it must do so by + // returning InputFormatterResult.Success(defaultForModelType), because input formatters + // are responsible for choosing a default value for the model type. + var message = bindingContext + .ModelMetadata + .ModelBindingMessageProvider + .MissingRequestBodyRequiredValueAccessor(); + bindingContext.ModelState.AddModelError(modelBindingKey, message); + } + } + catch (Exception exception) when (exception is InputFormatterException || ShouldHandleException(formatter)) + { + bindingContext.ModelState.AddModelError(modelBindingKey, exception, bindingContext.ModelMetadata); + } + + _logger?.DoneAttemptingToBindModel(bindingContext); + } + + private bool ShouldHandleException(IInputFormatter formatter) + { + var policy = _options.InputFormatterExceptionPolicy; + + // Any explicit policy on the formatters takes precedence over the global policy on MvcOptions + if (formatter is IInputFormatterExceptionPolicy exceptionPolicy) + { + policy = exceptionPolicy.ExceptionPolicy; + } + + return policy == InputFormatterExceptionPolicy.AllExceptions; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinderProvider.cs new file mode 100644 index 0000000000..c2110c793e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinderProvider.cs @@ -0,0 +1,98 @@ +// Copyright (c) .NET 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.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for deserializing the request body using a formatter. + /// + public class BodyModelBinderProvider : IModelBinderProvider + { + private readonly IList _formatters; + private readonly IHttpRequestStreamReaderFactory _readerFactory; + private readonly ILoggerFactory _loggerFactory; + private readonly MvcOptions _options; + + /// + /// Creates a new . + /// + /// The list of . + /// The . + public BodyModelBinderProvider(IList formatters, IHttpRequestStreamReaderFactory readerFactory) + : this(formatters, readerFactory, loggerFactory: null) + { + } + + /// + /// Creates a new . + /// + /// The list of . + /// The . + /// The . + public BodyModelBinderProvider(IList formatters, IHttpRequestStreamReaderFactory readerFactory, ILoggerFactory loggerFactory) + : this(formatters, readerFactory, loggerFactory, options: null) + { + } + + /// + /// Creates a new . + /// + /// The list of . + /// The . + /// The . + /// The . + public BodyModelBinderProvider( + IList formatters, + IHttpRequestStreamReaderFactory readerFactory, + ILoggerFactory loggerFactory, + MvcOptions options) + { + if (formatters == null) + { + throw new ArgumentNullException(nameof(formatters)); + } + + if (readerFactory == null) + { + throw new ArgumentNullException(nameof(readerFactory)); + } + + _formatters = formatters; + _readerFactory = readerFactory; + _loggerFactory = loggerFactory; + _options = options; + } + + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.BindingInfo.BindingSource != null && + context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Body)) + { + if (_formatters.Count == 0) + { + throw new InvalidOperationException(Resources.FormatInputFormattersAreRequired( + typeof(MvcOptions).FullName, + nameof(MvcOptions.InputFormatters), + typeof(IInputFormatter).FullName)); + } + + return new BodyModelBinder(_formatters, _readerFactory, _loggerFactory, _options); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinder.cs new file mode 100644 index 0000000000..99461f9ec9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinder.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; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// ModelBinder to bind byte Arrays. + /// + public class ByteArrayModelBinder : IModelBinder + { + private readonly ILogger _logger; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that takes an . + /// Initializes a new instance of . + /// + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that takes an " + nameof(ILoggerFactory) + ".")] + public ByteArrayModelBinder() + : this(NullLoggerFactory.Instance) + { + } + + /// + /// Initializes a new instance of . + /// + /// The . + public ByteArrayModelBinder(ILoggerFactory loggerFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _logger = loggerFactory.CreateLogger(); + } + + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + _logger.AttemptingToBindModel(bindingContext); + + // Check for missing data case 1: There was no element containing this data. + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + if (valueProviderResult == ValueProviderResult.None) + { + _logger.FoundNoValueInRequest(bindingContext); + _logger.DoneAttemptingToBindModel(bindingContext); + return Task.CompletedTask; + } + + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); + + // Check for missing data case 2: There was an element but it was left blank. + var value = valueProviderResult.FirstValue; + if (string.IsNullOrEmpty(value)) + { + _logger.FoundNoValueInRequest(bindingContext); + _logger.DoneAttemptingToBindModel(bindingContext); + return Task.CompletedTask; + } + + try + { + var model = Convert.FromBase64String(value); + bindingContext.Result = ModelBindingResult.Success(model); + } + catch (Exception exception) + { + bindingContext.ModelState.TryAddModelError( + bindingContext.ModelName, + exception, + bindingContext.ModelMetadata); + } + + _logger.DoneAttemptingToBindModel(bindingContext); + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinderProvider.cs new file mode 100644 index 0000000000..402f796640 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinderProvider.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; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for binding base64 encoded byte arrays. + /// + public class ByteArrayModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.Metadata.ModelType == typeof(byte[])) + { + var loggerFactory = context.Services.GetRequiredService(); + return new ByteArrayModelBinder(loggerFactory); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinder.cs new file mode 100644 index 0000000000..b8b9493ffe --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinder.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.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// implementation to bind models of type . + /// + public class CancellationTokenModelBinder : IModelBinder + { + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + // We need to force boxing now, so we can insert the same reference to the boxed CancellationToken + // in both the ValidationState and ModelBindingResult. + // + // DO NOT simplify this code by removing the cast. + var model = (object)bindingContext.HttpContext.RequestAborted; + bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true }); + bindingContext.Result = ModelBindingResult.Success(model); + + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinderProvider.cs new file mode 100644 index 0000000000..b3ee6f7361 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinderProvider.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.Threading; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for . + /// + public class CancellationTokenModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.Metadata.ModelType == typeof(CancellationToken)) + { + return new CancellationTokenModelBinder(); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinder.cs new file mode 100644 index 0000000000..16834ebadc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinder.cs @@ -0,0 +1,417 @@ +// Copyright (c) .NET 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.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// implementation for binding collection values. + /// + /// Type of elements in the collection. + public class CollectionModelBinder : ICollectionModelBinder + { + private static readonly IValueProvider EmptyValueProvider = new CompositeValueProvider(); + private Func _modelCreator; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that also takes an . + /// Creates a new . + /// + /// The for binding elements. + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that also takes an " + nameof(ILoggerFactory) + ".")] + public CollectionModelBinder(IModelBinder elementBinder) + : this(elementBinder, NullLoggerFactory.Instance) + { + } + + /// + /// Creates a new . + /// + /// The for binding elements. + /// The . + public CollectionModelBinder(IModelBinder elementBinder, ILoggerFactory loggerFactory) + { + if (elementBinder == null) + { + throw new ArgumentNullException(nameof(elementBinder)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + ElementBinder = elementBinder; + Logger = loggerFactory.CreateLogger(GetType()); + } + + /// + /// Gets the instances for binding collection elements. + /// + protected IModelBinder ElementBinder { get; } + + /// + /// The used for logging in this binder. + /// + protected ILogger Logger { get; } + + /// + public virtual async Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + Logger.AttemptingToBindModel(bindingContext); + + var model = bindingContext.Model; + if (!bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) + { + Logger.FoundNoValueInRequest(bindingContext); + + // If we failed to find data for a top-level model, then generate a + // default 'empty' model (or use existing Model) and return it. + if (bindingContext.IsTopLevelObject) + { + if (model == null) + { + model = CreateEmptyCollection(bindingContext.ModelType); + } + + bindingContext.Result = ModelBindingResult.Success(model); + } + + Logger.DoneAttemptingToBindModel(bindingContext); + return; + } + + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + + CollectionResult result; + if (valueProviderResult == ValueProviderResult.None) + { + Logger.NoNonIndexBasedFormatFoundForCollection(bindingContext); + result = await BindComplexCollection(bindingContext); + } + else + { + result = await BindSimpleCollection(bindingContext, valueProviderResult); + } + + var boundCollection = result.Model; + if (model == null) + { + model = ConvertToCollectionType(bindingContext.ModelType, boundCollection); + } + else + { + // Special case for TryUpdateModelAsync(collection, ...) scenarios. Model is null in all other cases. + CopyToModel(model, boundCollection); + } + + Debug.Assert(model != null); + if (result.ValidationStrategy != null) + { + bindingContext.ValidationState.Add(model, new ValidationStateEntry() + { + Strategy = result.ValidationStrategy, + }); + } + + if (valueProviderResult != ValueProviderResult.None) + { + // If we did simple binding, then modelstate should be updated to reflect what we bound for ModelName. + // If we did complex binding, there will already be an entry for each index. + bindingContext.ModelState.SetModelValue( + bindingContext.ModelName, + valueProviderResult); + } + + bindingContext.Result = ModelBindingResult.Success(model); + Logger.DoneAttemptingToBindModel(bindingContext); + } + + /// + public virtual bool CanCreateInstance(Type targetType) + { + if (targetType.IsAssignableFrom(typeof(List))) + { + // Simple case such as ICollection, IEnumerable and IList. + return true; + } + + return targetType.GetTypeInfo().IsClass && + !targetType.GetTypeInfo().IsAbstract && + typeof(ICollection).IsAssignableFrom(targetType); + } + + /// + /// Create an assignable to . + /// + /// of the model. + /// An assignable to . + /// Called when creating a default 'empty' model for a top level bind. + protected virtual object CreateEmptyCollection(Type targetType) + { + if (targetType.IsAssignableFrom(typeof(List))) + { + // Simple case such as ICollection, IEnumerable and IList. + return new List(); + } + + return CreateInstance(targetType); + } + + /// + /// Create an instance of . + /// + /// of the model. + /// An instance of . + protected object CreateInstance(Type targetType) + { + if (_modelCreator == null) + { + _modelCreator = Expression + .Lambda>(Expression.New(targetType)) + .Compile(); + } + + return _modelCreator(); + + } + + // Used when the ValueProvider contains the collection to be bound as a single element, e.g. the raw value + // is [ "1", "2" ] and needs to be converted to an int[]. + // Internal for testing. + internal async Task BindSimpleCollection( + ModelBindingContext bindingContext, + ValueProviderResult values) + { + var boundCollection = new List(); + + var elementMetadata = bindingContext.ModelMetadata.ElementMetadata; + + foreach (var value in values) + { + bindingContext.ValueProvider = new CompositeValueProvider + { + // our temporary provider goes at the front of the list + new ElementalValueProvider(bindingContext.ModelName, value, values.Culture), + bindingContext.ValueProvider + }; + + // Enter new scope to change ModelMetadata and isolate element binding operations. + using (bindingContext.EnterNestedScope( + elementMetadata, + fieldName: bindingContext.FieldName, + modelName: bindingContext.ModelName, + model: null)) + { + await ElementBinder.BindModelAsync(bindingContext); + + if (bindingContext.Result.IsModelSet) + { + var boundValue = bindingContext.Result.Model; + boundCollection.Add(ModelBindingHelper.CastOrDefault(boundValue)); + } + } + } + + return new CollectionResult + { + Model = boundCollection + }; + } + + // Used when the ValueProvider contains the collection to be bound as multiple elements, e.g. foo[0], foo[1]. + private Task BindComplexCollection(ModelBindingContext bindingContext) + { + Logger.AttemptingToBindCollectionUsingIndices(bindingContext); + + var indexPropertyName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "index"); + + // Remove any value provider that may not use indexPropertyName as-is. Don't match e.g. Model[index]. + var valueProvider = bindingContext.ValueProvider; + if (valueProvider is IKeyRewriterValueProvider keyRewriterValueProvider) + { + valueProvider = keyRewriterValueProvider.Filter() ?? EmptyValueProvider; + } + + var valueProviderResultIndex = valueProvider.GetValue(indexPropertyName); + var indexNames = GetIndexNamesFromValueProviderResult(valueProviderResultIndex); + + return BindComplexCollectionFromIndexes(bindingContext, indexNames); + } + + // Internal for testing. + internal async Task BindComplexCollectionFromIndexes( + ModelBindingContext bindingContext, + IEnumerable indexNames) + { + bool indexNamesIsFinite; + if (indexNames != null) + { + indexNamesIsFinite = true; + } + else + { + indexNamesIsFinite = false; + indexNames = Enumerable.Range(0, int.MaxValue) + .Select(i => i.ToString(CultureInfo.InvariantCulture)); + } + + var elementMetadata = bindingContext.ModelMetadata.ElementMetadata; + + var boundCollection = new List(); + + foreach (var indexName in indexNames) + { + var fullChildName = ModelNames.CreateIndexModelName(bindingContext.ModelName, indexName); + + ModelBindingResult? result; + using (bindingContext.EnterNestedScope( + elementMetadata, + fieldName: indexName, + modelName: fullChildName, + model: null)) + { + await ElementBinder.BindModelAsync(bindingContext); + result = bindingContext.Result; + } + + var didBind = false; + object boundValue = null; + if (result != null && result.Value.IsModelSet) + { + didBind = true; + boundValue = result.Value.Model; + } + + // infinite size collection stops on first bind failure + if (!didBind && !indexNamesIsFinite) + { + break; + } + + boundCollection.Add(ModelBindingHelper.CastOrDefault(boundValue)); + } + + return new CollectionResult + { + Model = boundCollection, + + // If we're working with a fixed set of indexes then this is the format like: + // + // ?parameter.index=zero¶meter.index=one¶meter.index=two¶meter[zero]=0¶meter[one]=1¶meter[two]=2... + // + // We need to provide this data to the validation system so it can 'replay' the keys. + // But we can't just set ValidationState here, because it needs the 'real' model. + ValidationStrategy = indexNamesIsFinite ? + new ExplicitIndexCollectionValidationStrategy(indexNames) : + null, + }; + } + + // Internal for testing. + internal class CollectionResult + { + public IEnumerable Model { get; set; } + + public IValidationStrategy ValidationStrategy { get; set; } + } + + /// + /// Gets an assignable to that contains members from + /// . + /// + /// of the model. + /// + /// Collection of values retrieved from value providers. if nothing was bound. + /// + /// + /// An assignable to . if nothing + /// was bound. + /// + /// + /// Extensibility point that allows the bound collection to be manipulated or transformed before being + /// returned from the binder. + /// + protected virtual object ConvertToCollectionType(Type targetType, IEnumerable collection) + { + if (collection == null) + { + return null; + } + + if (targetType.IsAssignableFrom(typeof(List))) + { + // Depends on fact BindSimpleCollection() and BindComplexCollection() always return a List + // instance or null. + return collection; + } + + var newCollection = CreateInstance(targetType); + CopyToModel(newCollection, collection); + + return newCollection; + } + + /// + /// Adds values from to given . + /// + /// into which values are copied. + /// + /// Collection of values retrieved from value providers. if nothing was bound. + /// + protected virtual void CopyToModel(object target, IEnumerable sourceCollection) + { + if (target == null) + { + throw new ArgumentNullException(nameof(target)); + } + + var targetCollection = target as ICollection; + Debug.Assert(targetCollection != null, "This binder is instantiated only for ICollection model types."); + + if (sourceCollection != null && targetCollection != null && !targetCollection.IsReadOnly) + { + targetCollection.Clear(); + foreach (var element in sourceCollection) + { + targetCollection.Add(element); + } + } + } + + private static IEnumerable GetIndexNamesFromValueProviderResult(ValueProviderResult valueProviderResult) + { + IEnumerable indexNames = null; + if (valueProviderResult != null) + { + var indexes = (string[])valueProviderResult; + if (indexes != null && indexes.Length > 0) + { + indexNames = indexes; + } + } + + return indexNames; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs new file mode 100644 index 0000000000..0617122b5f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.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.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for . + /// + public class CollectionModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var modelType = context.Metadata.ModelType; + + // Arrays are handled by another binder. + if (modelType.IsArray) + { + return null; + } + + var loggerFactory = context.Services.GetRequiredService(); + + // 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) + { + var elementType = collectionType.GenericTypeArguments[0]; + var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType)); + + var binderType = typeof(CollectionModelBinder<>).MakeGenericType(collectionType.GenericTypeArguments); + return (IModelBinder)Activator.CreateInstance(binderType, elementBinder, loggerFactory); + } + + // 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 + // IReadOnlyList<>). + var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(IEnumerable<>)); + if (enumerableType != null) + { + var listType = typeof(List<>).MakeGenericType(enumerableType.GenericTypeArguments); + if (modelType.GetTypeInfo().IsAssignableFrom(listType.GetTypeInfo())) + { + var elementType = enumerableType.GenericTypeArguments[0]; + var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType)); + + var binderType = typeof(CollectionModelBinder<>).MakeGenericType(enumerableType.GenericTypeArguments); + return (IModelBinder)Activator.CreateInstance(binderType, elementBinder, loggerFactory); + } + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs new file mode 100644 index 0000000000..dd02365c96 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs @@ -0,0 +1,469 @@ +// Copyright (c) .NET 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 System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// implementation for binding complex types. + /// + public class ComplexTypeModelBinder : IModelBinder + { + private readonly IDictionary _propertyBinders; + private readonly ILogger _logger; + private Func _modelCreator; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that also takes an . + /// Creates a new . + /// + /// + /// The of binders to use for binding properties. + /// + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that also takes an " + nameof(ILoggerFactory) + ".")] + public ComplexTypeModelBinder(IDictionary propertyBinders) + : this(propertyBinders, NullLoggerFactory.Instance) + { + } + + /// + /// Creates a new . + /// + /// + /// The of binders to use for binding properties. + /// + /// The . + public ComplexTypeModelBinder( + IDictionary propertyBinders, + ILoggerFactory loggerFactory) + { + if (propertyBinders == null) + { + throw new ArgumentNullException(nameof(propertyBinders)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _propertyBinders = propertyBinders; + _logger = loggerFactory.CreateLogger(); + } + + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + _logger.AttemptingToBindModel(bindingContext); + + if (!CanCreateModel(bindingContext)) + { + return Task.CompletedTask; + } + + // Perf: separated to avoid allocating a state machine when we don't + // need to go async. + return BindModelCoreAsync(bindingContext); + } + + private async Task BindModelCoreAsync(ModelBindingContext bindingContext) + { + // Create model first (if necessary) to avoid reporting errors about properties when activation fails. + if (bindingContext.Model == null) + { + bindingContext.Model = CreateModel(bindingContext); + } + + for (var i = 0; i < bindingContext.ModelMetadata.Properties.Count; i++) + { + var property = bindingContext.ModelMetadata.Properties[i]; + if (!CanBindProperty(bindingContext, property)) + { + continue; + } + + // Pass complex (including collection) values down so that binding system does not unnecessarily + // recreate instances or overwrite inner properties that are not bound. No need for this with simple + // values because they will be overwritten if binding succeeds. Arrays are never reused because they + // cannot be resized. + object propertyModel = null; + if (property.PropertyGetter != null && + property.IsComplexType && + !property.ModelType.IsArray) + { + propertyModel = property.PropertyGetter(bindingContext.Model); + } + + var fieldName = property.BinderModelName ?? property.PropertyName; + var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); + + ModelBindingResult result; + using (bindingContext.EnterNestedScope( + modelMetadata: property, + fieldName: fieldName, + modelName: modelName, + model: propertyModel)) + { + await BindProperty(bindingContext); + result = bindingContext.Result; + } + + if (result.IsModelSet) + { + SetProperty(bindingContext, modelName, property, result); + } + else if (property.IsBindingRequired) + { + var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName); + bindingContext.ModelState.TryAddModelError(modelName, message); + } + } + + bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); + _logger.DoneAttemptingToBindModel(bindingContext); + } + + /// + /// Gets a value indicating whether or not the model property identified by + /// can be bound. + /// + /// The for the container model. + /// The for the model property. + /// true if the model property can be bound, otherwise false. + protected virtual bool CanBindProperty(ModelBindingContext bindingContext, ModelMetadata propertyMetadata) + { + var metadataProviderFilter = bindingContext.ModelMetadata.PropertyFilterProvider?.PropertyFilter; + if (metadataProviderFilter?.Invoke(propertyMetadata) == false) + { + return false; + } + + if (bindingContext.PropertyFilter?.Invoke(propertyMetadata) == false) + { + return false; + } + + if (!propertyMetadata.IsBindingAllowed) + { + return false; + } + + if (!CanUpdatePropertyInternal(propertyMetadata)) + { + return false; + } + + return true; + } + + /// + /// Attempts to bind a property of the model. + /// + /// The for the model property. + /// + /// A that when completed will set to the + /// result of model binding. + /// + protected virtual Task BindProperty(ModelBindingContext bindingContext) + { + var binder = _propertyBinders[bindingContext.ModelMetadata]; + return binder.BindModelAsync(bindingContext); + } + + internal bool CanCreateModel(ModelBindingContext bindingContext) + { + var isTopLevelObject = bindingContext.IsTopLevelObject; + + // If we get here the model is a complex object which was not directly bound by any previous model binder, + // so we want to decide if we want to continue binding. This is important to get right to avoid infinite + // recursion. + // + // First, we want to make sure this object is allowed to come from a value provider source as this binder + // will only include value provider data. For instance if the model is marked with [FromBody], then we + // can just skip it. A greedy source cannot be a value provider. + // + // If the model isn't marked with ANY binding source, then we assume it's OK also. + // + // We skip this check if it is a top level object because we want to always evaluate + // the creation of top level object (this is also required for ModelBinderAttribute to work.) + var bindingSource = bindingContext.BindingSource; + if (!isTopLevelObject && bindingSource != null && bindingSource.IsGreedy) + { + return false; + } + + // Create the object if: + // 1. It is a top level model. + if (isTopLevelObject) + { + return true; + } + + // 2. Any of the model properties can be bound using a value provider. + if (CanValueBindAnyModelProperties(bindingContext)) + { + return true; + } + + return false; + } + + private bool CanValueBindAnyModelProperties(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. + if (bindingContext.ModelMetadata.Properties.Count == 0) + { + _logger.NoPublicSettableProperties(bindingContext); + 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. + // + // 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 + // 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 non-greedy source, then we'll filter the + // the value providers to just that source, and see if we can find a matching prefix + // (see CanBindValue). + // + // 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, + // 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]; + if (!CanBindProperty(bindingContext, propertyMetadata)) + { + continue; + } + + hasBindableProperty = true; + + // This check will skip properties which are marked explicitly using a non value binder. + var bindingSource = propertyMetadata.BindingSource; + if (bindingSource == null || !bindingSource.IsGreedy) + { + isAnyPropertyEnabledForValueProviderBasedBinding = 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)) + { + // If any property can be bound from a value provider then continue. + if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) + { + 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; + } + + // Internal for tests + internal static bool CanUpdatePropertyInternal(ModelMetadata propertyMetadata) + { + return !propertyMetadata.IsReadOnly || CanUpdateReadOnlyProperty(propertyMetadata.ModelType); + } + + private static bool CanUpdateReadOnlyProperty(Type propertyType) + { + // Value types have copy-by-value semantics, which prevents us from updating + // properties that are marked readonly. + if (propertyType.GetTypeInfo().IsValueType) + { + return false; + } + + // Arrays are strange beasts since their contents are mutable but their sizes aren't. + // Therefore we shouldn't even try to update these. Further reading: + // http://blogs.msdn.com/ericlippert/archive/2008/09/22/arrays-considered-somewhat-harmful.aspx + if (propertyType.IsArray) + { + return false; + } + + // Special-case known immutable reference types + if (propertyType == typeof(string)) + { + return false; + } + + return true; + } + + /// + /// Creates suitable for given . + /// + /// The . + /// An compatible with . + protected virtual object CreateModel(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + // If model creator throws an exception, we want to propagate it back up the call stack, since the + // application developer should know that this was an invalid type to try to bind to. + if (_modelCreator == null) + { + // The following check causes the ComplexTypeModelBinder to NOT participate in binding structs as + // reflection does not provide information about the implicit parameterless constructor for a struct. + // This binder would eventually fail to construct an instance of the struct as the Linq's NewExpression + // compile fails to construct it. + var modelTypeInfo = bindingContext.ModelType.GetTypeInfo(); + if (modelTypeInfo.IsAbstract || modelTypeInfo.GetConstructor(Type.EmptyTypes) == null) + { + var metadata = bindingContext.ModelMetadata; + switch (metadata.MetadataKind) + { + case ModelMetadataKind.Parameter: + throw new InvalidOperationException( + Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForParameter( + modelTypeInfo.FullName, + metadata.ParameterName)); + case ModelMetadataKind.Property: + throw new InvalidOperationException( + Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForProperty( + modelTypeInfo.FullName, + metadata.PropertyName, + bindingContext.ModelMetadata.ContainerType.FullName)); + case ModelMetadataKind.Type: + throw new InvalidOperationException( + Resources.FormatComplexTypeModelBinder_NoParameterlessConstructor_ForType( + modelTypeInfo.FullName)); + } + } + + _modelCreator = Expression + .Lambda>(Expression.New(bindingContext.ModelType)) + .Compile(); + } + + return _modelCreator(); + } + + /// + /// Updates a property in the current . + /// + /// The . + /// The model name. + /// The for the property to set. + /// The for the property's new value. + protected virtual void SetProperty( + ModelBindingContext bindingContext, + string modelName, + ModelMetadata propertyMetadata, + ModelBindingResult result) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + if (modelName == null) + { + throw new ArgumentNullException(nameof(modelName)); + } + + if (propertyMetadata == null) + { + throw new ArgumentNullException(nameof(propertyMetadata)); + } + + if (!result.IsModelSet) + { + // If we don't have a value, don't set it on the model and trounce a pre-initialized value. + return; + } + + if (propertyMetadata.IsReadOnly) + { + // The property should have already been set when we called BindPropertyAsync, so there's + // nothing to do here. + return; + } + + var value = result.Model; + try + { + propertyMetadata.PropertySetter(bindingContext.Model, value); + } + catch (Exception exception) + { + AddModelError(exception, modelName, bindingContext); + } + } + + private static void AddModelError( + Exception exception, + string modelName, + ModelBindingContext bindingContext) + { + var targetInvocationException = exception as TargetInvocationException; + if (targetInvocationException?.InnerException != null) + { + exception = targetInvocationException.InnerException; + } + + // Do not add an error message if a binding error has already occurred for this property. + var modelState = bindingContext.ModelState; + var validationState = modelState.GetFieldValidationState(modelName); + if (validationState == ModelValidationState.Unvalidated) + { + modelState.AddModelError(modelName, exception, bindingContext.ModelMetadata); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs new file mode 100644 index 0000000000..f0d6b5989c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.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.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for complex types. + /// + public class ComplexTypeModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType) + { + var propertyBinders = new Dictionary(); + for (var i = 0; i < context.Metadata.Properties.Count; i++) + { + var property = context.Metadata.Properties[i]; + propertyBinders.Add(property, context.CreateBinder(property)); + } + + var loggerFactory = context.Services.GetRequiredService(); + return new ComplexTypeModelBinder(propertyBinders, loggerFactory); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DecimalModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DecimalModelBinder.cs new file mode 100644 index 0000000000..7692baa009 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DecimalModelBinder.cs @@ -0,0 +1,133 @@ +// Copyright (c) .NET 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.Runtime.ExceptionServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for and where T is + /// . + /// + public class DecimalModelBinder : IModelBinder + { + private readonly NumberStyles _supportedStyles; + private readonly ILogger _logger; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that also takes an . + /// Initializes a new instance of . + /// + /// The . + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that also takes an " + nameof(ILoggerFactory) + ".")] + public DecimalModelBinder(NumberStyles supportedStyles) + : this(supportedStyles, NullLoggerFactory.Instance) + { + } + + /// + /// Initializes a new instance of . + /// + /// The . + /// The . + public DecimalModelBinder(NumberStyles supportedStyles, ILoggerFactory loggerFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _supportedStyles = supportedStyles; + _logger = loggerFactory.CreateLogger(); + } + + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + _logger.AttemptingToBindModel(bindingContext); + + var modelName = bindingContext.ModelName; + var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); + if (valueProviderResult == ValueProviderResult.None) + { + _logger.FoundNoValueInRequest(bindingContext); + + // no entry + _logger.DoneAttemptingToBindModel(bindingContext); + return Task.CompletedTask; + } + + var modelState = bindingContext.ModelState; + modelState.SetModelValue(modelName, valueProviderResult); + + var metadata = bindingContext.ModelMetadata; + var type = metadata.UnderlyingOrModelType; + try + { + var value = valueProviderResult.FirstValue; + var culture = valueProviderResult.Culture; + + object model; + if (string.IsNullOrWhiteSpace(value)) + { + // Parse() method trims the value (with common NumberStyles) then throws if the result is empty. + model = null; + } + else if (type == typeof(decimal)) + { + model = decimal.Parse(value, _supportedStyles, culture); + } + else + { + // unreachable + throw new NotSupportedException(); + } + + // When converting value, a null model may indicate a failed conversion for an otherwise required + // model (can't set a ValueType to null). This detects if a null model value is acceptable given the + // current bindingContext. If not, an error is logged. + if (model == null && !metadata.IsReferenceOrNullableType) + { + modelState.TryAddModelError( + modelName, + metadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor( + valueProviderResult.ToString())); + } + else + { + bindingContext.Result = ModelBindingResult.Success(model); + } + } + catch (Exception exception) + { + var isFormatException = exception is FormatException; + if (!isFormatException && exception.InnerException != null) + { + // Unlike TypeConverters, floating point types do not seem to wrap FormatExceptions. Preserve + // this code in case a cursory review of the CoreFx code missed something. + exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException; + } + + modelState.TryAddModelError(modelName, exception, metadata); + + // Conversion failed. + } + + _logger.DoneAttemptingToBindModel(bindingContext); + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinder.cs new file mode 100644 index 0000000000..fbb0c53db1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinder.cs @@ -0,0 +1,194 @@ +// Copyright (c) .NET 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.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// implementation for binding dictionary values. + /// + /// Type of keys in the dictionary. + /// Type of values in the dictionary. + public class DictionaryModelBinder : CollectionModelBinder> + { + private readonly IModelBinder _valueBinder; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that also takes an . + /// Creates a new . + /// + /// The for . + /// The for . + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that also takes an " + nameof(ILoggerFactory) + ".")] + public DictionaryModelBinder(IModelBinder keyBinder, IModelBinder valueBinder) + : this(keyBinder, valueBinder, NullLoggerFactory.Instance) + { + } + + /// + /// Creates a new . + /// + /// The for . + /// The for . + /// The . + public DictionaryModelBinder(IModelBinder keyBinder, IModelBinder valueBinder, ILoggerFactory loggerFactory) + : base(new KeyValuePairModelBinder(keyBinder, valueBinder, loggerFactory), loggerFactory) + { + if (valueBinder == null) + { + throw new ArgumentNullException(nameof(valueBinder)); + } + + _valueBinder = valueBinder; + } + + /// + public override async Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + await base.BindModelAsync(bindingContext); + if (!bindingContext.Result.IsModelSet) + { + // No match for the prefix at all. + return; + } + + var result = bindingContext.Result; + + Debug.Assert(result.Model != null); + var model = (IDictionary)result.Model; + if (model.Count != 0) + { + // ICollection> approach was successful. + return; + } + + Logger.NoKeyValueFormatForDictionaryModelBinder(bindingContext); + + if (!(bindingContext.ValueProvider is IEnumerableValueProvider enumerableValueProvider)) + { + // No IEnumerableValueProvider available for the fallback approach. For example the user may have + // replaced the ValueProvider with something other than a CompositeValueProvider. + return; + } + + // Attempt to bind dictionary from a set of prefix[key]=value entries. Get the short and long keys first. + var prefix = bindingContext.ModelName; + var keys = enumerableValueProvider.GetKeysFromPrefix(prefix); + if (keys.Count == 0) + { + // No entries with the expected keys. + return; + } + + // Update the existing successful but empty ModelBindingResult. + var elementMetadata = bindingContext.ModelMetadata.ElementMetadata; + var valueMetadata = elementMetadata.Properties[nameof(KeyValuePair.Value)]; + + var keyMappings = new Dictionary(StringComparer.Ordinal); + foreach (var kvp in keys) + { + // Use InvariantCulture to convert the key since ExpressionHelper.GetExpressionText() would use + // that culture when rendering a form. + var convertedKey = ModelBindingHelper.ConvertTo(kvp.Key, culture: null); + + using (bindingContext.EnterNestedScope( + modelMetadata: valueMetadata, + fieldName: bindingContext.FieldName, + modelName: kvp.Value, + model: null)) + { + await _valueBinder.BindModelAsync(bindingContext); + + var valueResult = bindingContext.Result; + if (!valueResult.IsModelSet) + { + // Factories for IKeyRewriterValueProvider implementations are not all-or-nothing i.e. + // "[key][propertyName]" may be rewritten as ".key.propertyName" or "[key].propertyName". Try + // again in case this scope is binding a complex type and rewriting + // landed on ".key.propertyName" or in case this scope is binding another collection and an + // IKeyRewriterValueProvider implementation was first (hiding the original "[key][next key]"). + if (kvp.Value.EndsWith("]")) + { + bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, kvp.Key); + } + else + { + bindingContext.ModelName = ModelNames.CreateIndexModelName(prefix, kvp.Key); + } + + await _valueBinder.BindModelAsync(bindingContext); + valueResult = bindingContext.Result; + } + + // Always add an entry to the dictionary but validate only if binding was successful. + model[convertedKey] = ModelBindingHelper.CastOrDefault(valueResult.Model); + keyMappings.Add(bindingContext.ModelName, convertedKey); + } + } + + bindingContext.ValidationState.Add(model, new ValidationStateEntry() + { + Strategy = new ShortFormDictionaryValidationStrategy(keyMappings, valueMetadata), + }); + } + + /// + protected override object ConvertToCollectionType( + Type targetType, + IEnumerable> collection) + { + if (collection == null) + { + return null; + } + + if (targetType.IsAssignableFrom(typeof(Dictionary))) + { + // Collection is a List>, never already a Dictionary. + return collection.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + } + + return base.ConvertToCollectionType(targetType, collection); + } + + /// + protected override object CreateEmptyCollection(Type targetType) + { + if (targetType.IsAssignableFrom(typeof(Dictionary))) + { + // Simple case such as IDictionary. + return new Dictionary(); + } + + return base.CreateEmptyCollection(targetType); + } + + public override bool CanCreateInstance(Type targetType) + { + if (targetType.IsAssignableFrom(typeof(Dictionary))) + { + // Simple case such as IDictionary. + return true; + } + + return base.CanCreateInstance(targetType); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinderProvider.cs new file mode 100644 index 0000000000..0d7db7c064 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinderProvider.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.Collections.Generic; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for binding . + /// + public class DictionaryModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var modelType = context.Metadata.ModelType; + var dictionaryType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(IDictionary<,>)); + if (dictionaryType != null) + { + var keyType = dictionaryType.GenericTypeArguments[0]; + var keyBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(keyType)); + + var valueType = dictionaryType.GenericTypeArguments[1]; + var valueBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(valueType)); + + var binderType = typeof(DictionaryModelBinder<,>).MakeGenericType(dictionaryType.GenericTypeArguments); + var loggerFactory = context.Services.GetRequiredService(); + return (IModelBinder)Activator.CreateInstance(binderType, keyBinder, valueBinder, loggerFactory); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DoubleModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DoubleModelBinder.cs new file mode 100644 index 0000000000..77d32d5a0d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DoubleModelBinder.cs @@ -0,0 +1,133 @@ +// Copyright (c) .NET 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.Runtime.ExceptionServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for and where T is + /// . + /// + public class DoubleModelBinder : IModelBinder + { + private readonly NumberStyles _supportedStyles; + private readonly ILogger _logger; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that also takes an . + /// Initializes a new instance of . + /// + /// The . + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that also takes an " + nameof(ILoggerFactory) + ".")] + public DoubleModelBinder(NumberStyles supportedStyles) + : this(supportedStyles, NullLoggerFactory.Instance) + { + } + + /// + /// Initializes a new instance of . + /// + /// The . + /// The . + public DoubleModelBinder(NumberStyles supportedStyles, ILoggerFactory loggerFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _supportedStyles = supportedStyles; + _logger = loggerFactory.CreateLogger(); + } + + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + _logger.AttemptingToBindModel(bindingContext); + + var modelName = bindingContext.ModelName; + var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); + if (valueProviderResult == ValueProviderResult.None) + { + _logger.FoundNoValueInRequest(bindingContext); + + // no entry + _logger.DoneAttemptingToBindModel(bindingContext); + return Task.CompletedTask; + } + + var modelState = bindingContext.ModelState; + modelState.SetModelValue(modelName, valueProviderResult); + + var metadata = bindingContext.ModelMetadata; + var type = metadata.UnderlyingOrModelType; + try + { + var value = valueProviderResult.FirstValue; + var culture = valueProviderResult.Culture; + + object model; + if (string.IsNullOrWhiteSpace(value)) + { + // Parse() method trims the value (with common NumberStyles) then throws if the result is empty. + model = null; + } + else if (type == typeof(double)) + { + model = double.Parse(value, _supportedStyles, culture); + } + else + { + // unreachable + throw new NotSupportedException(); + } + + // When converting value, a null model may indicate a failed conversion for an otherwise required + // model (can't set a ValueType to null). This detects if a null model value is acceptable given the + // current bindingContext. If not, an error is logged. + if (model == null && !metadata.IsReferenceOrNullableType) + { + modelState.TryAddModelError( + modelName, + metadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor( + valueProviderResult.ToString())); + } + else + { + bindingContext.Result = ModelBindingResult.Success(model); + } + } + catch (Exception exception) + { + var isFormatException = exception is FormatException; + if (!isFormatException && exception.InnerException != null) + { + // Unlike TypeConverters, floating point types do not seem to wrap FormatExceptions. Preserve + // this code in case a cursory review of the CoreFx code missed something. + exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException; + } + + modelState.TryAddModelError(modelName, exception, metadata); + + // Conversion failed. + } + + _logger.DoneAttemptingToBindModel(bindingContext); + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs new file mode 100644 index 0000000000..601d02ba44 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.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.Globalization; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// implementation to bind models for types deriving from . + /// + public class EnumTypeModelBinder : SimpleTypeModelBinder + { + private readonly bool _suppressBindingUndefinedValueToEnumType; + + /// + /// Initializes a new instance of . + /// + /// + /// Flag to determine if binding to undefined should be suppressed or not. + /// + /// The model type. + /// The , + public EnumTypeModelBinder( + bool suppressBindingUndefinedValueToEnumType, + Type modelType, + ILoggerFactory loggerFactory) + : base(modelType, loggerFactory) + { + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _suppressBindingUndefinedValueToEnumType = suppressBindingUndefinedValueToEnumType; + } + + protected override void CheckModel( + ModelBindingContext bindingContext, + ValueProviderResult valueProviderResult, + object model) + { + if (model == null || !_suppressBindingUndefinedValueToEnumType) + { + base.CheckModel(bindingContext, valueProviderResult, model); + } + else + { + if (IsDefinedInEnum(model, bindingContext)) + { + bindingContext.Result = ModelBindingResult.Success(model); + } + else + { + bindingContext.ModelState.TryAddModelError( + bindingContext.ModelName, + bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueIsInvalidAccessor( + valueProviderResult.ToString())); + } + } + } + + private bool IsDefinedInEnum(object model, ModelBindingContext bindingContext) + { + var modelType = bindingContext.ModelMetadata.UnderlyingOrModelType; + + // Check if the converted value is indeed defined on the enum as EnumTypeConverter + // converts value to the backing type (ex: integer) and does not check if the value is defined on the enum. + if (bindingContext.ModelMetadata.IsFlagsEnum) + { + // Enum.IsDefined does not work with combined flag enum values. + // From EnumDataTypeAttribute.cs in CoreFX. + // Examples: + // + // [Flags] + // 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 + // + // Invalid Scenarios: + // 1. valueproviderresult="2,10", model=12, underlying=12, converted=12 + // + var underlying = Convert.ChangeType( + model, + Enum.GetUnderlyingType(modelType), + CultureInfo.InvariantCulture).ToString(); + var converted = model.ToString(); + return !string.Equals(underlying, converted, StringComparison.OrdinalIgnoreCase); + } + return Enum.IsDefined(modelType, model); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs new file mode 100644 index 0000000000..cd75fbb2fe --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.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; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// A for types deriving from . + /// + public class EnumTypeModelBinderProvider : IModelBinderProvider + { + private readonly MvcOptions _options; + + /// + /// Initializes a new instance of . + /// + /// The . + public EnumTypeModelBinderProvider(MvcOptions options) + { + _options = options; + } + + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.Metadata.IsEnum) + { + var loggerFactory = context.Services.GetRequiredService(); + return new EnumTypeModelBinder( + _options.SuppressBindingUndefinedValueToEnumType, + context.Metadata.UnderlyingOrModelType, + loggerFactory); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatModelBinder.cs new file mode 100644 index 0000000000..515266d2df --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatModelBinder.cs @@ -0,0 +1,133 @@ +// Copyright (c) .NET 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.Runtime.ExceptionServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for and where T is + /// . + /// + public class FloatModelBinder : IModelBinder + { + private readonly NumberStyles _supportedStyles; + private readonly ILogger _logger; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that also takes an . + /// Initializes a new instance of . + /// + /// The . + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that also takes an " + nameof(ILoggerFactory) + ".")] + public FloatModelBinder(NumberStyles supportedStyles) + : this(supportedStyles, NullLoggerFactory.Instance) + { + } + + /// + /// Initializes a new instance of . + /// + /// The . + /// The . + public FloatModelBinder(NumberStyles supportedStyles, ILoggerFactory loggerFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _supportedStyles = supportedStyles; + _logger = loggerFactory.CreateLogger(); + } + + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + _logger.AttemptingToBindModel(bindingContext); + + var modelName = bindingContext.ModelName; + var valueProviderResult = bindingContext.ValueProvider.GetValue(modelName); + if (valueProviderResult == ValueProviderResult.None) + { + _logger.FoundNoValueInRequest(bindingContext); + + // no entry + _logger.DoneAttemptingToBindModel(bindingContext); + return Task.CompletedTask; + } + + var modelState = bindingContext.ModelState; + modelState.SetModelValue(modelName, valueProviderResult); + + var metadata = bindingContext.ModelMetadata; + var type = metadata.UnderlyingOrModelType; + try + { + var value = valueProviderResult.FirstValue; + var culture = valueProviderResult.Culture; + + object model; + if (string.IsNullOrWhiteSpace(value)) + { + // Parse() method trims the value (with common NumberStyles) then throws if the result is empty. + model = null; + } + else if (type == typeof(float)) + { + model = float.Parse(value, _supportedStyles, culture); + } + else + { + // unreachable + throw new NotSupportedException(); + } + + // When converting value, a null model may indicate a failed conversion for an otherwise required + // model (can't set a ValueType to null). This detects if a null model value is acceptable given the + // current bindingContext. If not, an error is logged. + if (model == null && !metadata.IsReferenceOrNullableType) + { + modelState.TryAddModelError( + modelName, + metadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor( + valueProviderResult.ToString())); + } + else + { + bindingContext.Result = ModelBindingResult.Success(model); + } + } + catch (Exception exception) + { + var isFormatException = exception is FormatException; + if (!isFormatException && exception.InnerException != null) + { + // Unlike TypeConverters, floating point types do not seem to wrap FormatExceptions. Preserve + // this code in case a cursory review of the CoreFx code missed something. + exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException; + } + + modelState.TryAddModelError(modelName, exception, metadata); + + // Conversion failed. + } + + _logger.DoneAttemptingToBindModel(bindingContext); + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatingPointTypeModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatingPointTypeModelBinderProvider.cs new file mode 100644 index 0000000000..b881f66235 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatingPointTypeModelBinderProvider.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 System.Globalization; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for binding , , + /// , and their wrappers. + /// + public class FloatingPointTypeModelBinderProvider : IModelBinderProvider + { + // SimpleTypeModelBinder uses DecimalConverter and similar. Those TypeConverters default to NumberStyles.Float. + // Internal for testing. + internal static readonly NumberStyles SupportedStyles = NumberStyles.Float | NumberStyles.AllowThousands; + + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var modelType = context.Metadata.UnderlyingOrModelType; + var loggerFactory = context.Services.GetRequiredService(); + if (modelType == typeof(decimal)) + { + return new DecimalModelBinder(SupportedStyles, loggerFactory); + } + + if (modelType == typeof(double)) + { + return new DoubleModelBinder(SupportedStyles, loggerFactory); + } + + if (modelType == typeof(float)) + { + return new FloatModelBinder(SupportedStyles, loggerFactory); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinder.cs new file mode 100644 index 0000000000..41d96ed272 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinder.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; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// implementation to bind form values to . + /// + public class FormCollectionModelBinder : IModelBinder + { + private readonly ILogger _logger; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that takes an . + /// Initializes a new instance of . + /// + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that takes an " + nameof(ILoggerFactory) + ".")] + public FormCollectionModelBinder() + : this(NullLoggerFactory.Instance) + { + } + + /// + /// Initializes a new instance of . + /// + /// The . + public FormCollectionModelBinder(ILoggerFactory loggerFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _logger = loggerFactory.CreateLogger(); + } + + /// + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + _logger.AttemptingToBindModel(bindingContext); + + object model; + var request = bindingContext.HttpContext.Request; + if (request.HasFormContentType) + { + var form = await request.ReadFormAsync(); + model = form; + } + else + { + _logger.CannotBindToFilesCollectionDueToUnsupportedContentType(bindingContext); + model = new EmptyFormCollection(); + } + + bindingContext.Result = ModelBindingResult.Success(model); + _logger.DoneAttemptingToBindModel(bindingContext); + } + + private class EmptyFormCollection : IFormCollection + { + public StringValues this[string key] => StringValues.Empty; + + public int Count => 0; + + public IFormFileCollection Files => new EmptyFormFileCollection(); + + public ICollection Keys => new List(); + + public bool ContainsKey(string key) + { + return false; + } + + public IEnumerator> GetEnumerator() + { + return Enumerable.Empty>().GetEnumerator(); + } + + public bool TryGetValue(string key, out StringValues value) + { + value = default(StringValues); + return false; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + private class EmptyFormFileCollection : List, IFormFileCollection + { + public IFormFile this[string name] => null; + + public IFormFile GetFile(string name) => null; + + IReadOnlyList IFormFileCollection.GetFiles(string name) => null; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinderProvider.cs new file mode 100644 index 0000000000..edc91adfaf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinderProvider.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; +using System.Reflection; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for . + /// + public class FormCollectionModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var modelType = context.Metadata.ModelType; + + if (typeof(FormCollection).GetTypeInfo().IsAssignableFrom(modelType)) + { + throw new InvalidOperationException( + Resources.FormatFormCollectionModelBinder_CannotBindToFormCollection( + typeof(FormCollectionModelBinder).FullName, + modelType.FullName, + typeof(IFormCollection).FullName)); + } + + if (modelType == typeof(IFormCollection)) + { + var loggerFactory = context.Services.GetRequiredService(); + return new FormCollectionModelBinder(loggerFactory); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinder.cs new file mode 100644 index 0000000000..5a7f1157ac --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinder.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; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// implementation to bind posted files to . + /// + public class FormFileModelBinder : IModelBinder + { + private readonly ILogger _logger; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that takes an . + /// Initializes a new instance of . + /// + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that takes an " + nameof(ILoggerFactory) + ".")] + public FormFileModelBinder() + : this(NullLoggerFactory.Instance) + { + } + + /// + /// Initializes a new instance of . + /// + /// The . + public FormFileModelBinder(ILoggerFactory loggerFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _logger = loggerFactory.CreateLogger(); + } + + /// + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + _logger.AttemptingToBindModel(bindingContext); + + var createFileCollection = bindingContext.ModelType == typeof(IFormFileCollection); + if (!createFileCollection && !ModelBindingHelper.CanGetCompatibleCollection(bindingContext)) + { + // Silently fail if unable to create an instance or use the current instance. + return; + } + + ICollection postedFiles; + if (createFileCollection) + { + postedFiles = new List(); + } + else + { + postedFiles = ModelBindingHelper.GetCompatibleCollection(bindingContext); + } + + // If we're at the top level, then use the FieldName (parameter or property name). + // This handles the fact that there will be nothing in the ValueProviders for this parameter + // and so we'll do the right thing even though we 'fell-back' to the empty prefix. + var modelName = bindingContext.IsTopLevelObject + ? bindingContext.BinderModelName ?? bindingContext.FieldName + : bindingContext.ModelName; + + await GetFormFilesAsync(modelName, bindingContext, postedFiles); + + object value; + if (bindingContext.ModelType == typeof(IFormFile)) + { + if (postedFiles.Count == 0) + { + // Silently fail if the named file does not exist in the request. + _logger.DoneAttemptingToBindModel(bindingContext); + return; + } + + value = postedFiles.First(); + } + else + { + if (postedFiles.Count == 0 && !bindingContext.IsTopLevelObject) + { + // Silently fail if no files match. Will bind to an empty collection (treat empty as a success + // case and not reach here) if binding to a top-level object. + _logger.DoneAttemptingToBindModel(bindingContext); + return; + } + + // Perform any final type mangling needed. + var modelType = bindingContext.ModelType; + if (modelType == typeof(IFormFile[])) + { + Debug.Assert(postedFiles is List); + value = ((List)postedFiles).ToArray(); + } + else if (modelType == typeof(IFormFileCollection)) + { + Debug.Assert(postedFiles is List); + value = new FileCollection((List)postedFiles); + } + else + { + value = postedFiles; + } + } + + // We need to add a ValidationState entry because the modelName might be non-standard. Otherwise + // the entry we create in model state might not be marked as valid. + bindingContext.ValidationState.Add(value, new ValidationStateEntry() + { + Key = modelName, + }); + + bindingContext.ModelState.SetModelValue( + modelName, + rawValue: null, + attemptedValue: null); + + bindingContext.Result = ModelBindingResult.Success(value); + _logger.DoneAttemptingToBindModel(bindingContext); + } + + private async Task GetFormFilesAsync( + string modelName, + ModelBindingContext bindingContext, + ICollection postedFiles) + { + var request = bindingContext.HttpContext.Request; + if (request.HasFormContentType) + { + var form = await request.ReadFormAsync(); + + foreach (var file in form.Files) + { + // If there is an in the form and is left blank. + if (file.Length == 0 && string.IsNullOrEmpty(file.FileName)) + { + continue; + } + + if (file.Name.Equals(modelName, StringComparison.OrdinalIgnoreCase)) + { + postedFiles.Add(file); + } + } + + if (postedFiles.Count == 0) + { + _logger.NoFilesFoundInRequest(); + } + } + else + { + _logger.CannotBindToFilesCollectionDueToUnsupportedContentType(bindingContext); + } + } + + private class FileCollection : ReadOnlyCollection, IFormFileCollection + { + public FileCollection(List list) + : base(list) + { + } + + public IFormFile this[string name] => GetFile(name); + + public IFormFile GetFile(string name) + { + for (var i = 0; i < Items.Count; i++) + { + var file = Items[i]; + if (string.Equals(name, file.Name, StringComparison.OrdinalIgnoreCase)) + { + return file; + } + } + + return null; + } + + public IReadOnlyList GetFiles(string name) + { + var files = new List(); + for (var i = 0; i < Items.Count; i++) + { + var file = Items[i]; + if (string.Equals(name, file.Name, StringComparison.OrdinalIgnoreCase)) + { + files.Add(file); + } + } + + return files; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinderProvider.cs new file mode 100644 index 0000000000..2edd550fed --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinderProvider.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; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for , collections + /// of , and . + /// + public class FormFileModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // Note: This condition needs to be kept in sync with ApiBehaviorApplicationModelProvider. + var modelType = context.Metadata.ModelType; + if (modelType == typeof(IFormFile) || + modelType == typeof(IFormFileCollection) || + typeof(IEnumerable).IsAssignableFrom(modelType)) + { + var loggerFactory = context.Services.GetRequiredService(); + return new FormFileModelBinder(loggerFactory); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinder.cs new file mode 100644 index 0000000000..48c3ae2304 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinder.cs @@ -0,0 +1,242 @@ +// Copyright (c) .NET 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.Globalization; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An which binds models from the request headers when a model + /// has the binding source . + /// + public class HeaderModelBinder : IModelBinder + { + private readonly ILogger _logger; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that takes an and an . + /// Initializes a new instance of . + /// + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that takes an " + nameof(ILoggerFactory) + " and an " + nameof(IModelBinder) + ".")] + public HeaderModelBinder() + : this(NullLoggerFactory.Instance) + { + } + + /// + /// Initializes a new instance of . + /// + /// The . + public HeaderModelBinder(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + /// + /// Initializes a new instance of . + /// + /// The . + /// The which does the actual + /// binding of values. + public HeaderModelBinder(ILoggerFactory loggerFactory, IModelBinder innerModelBinder) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + if (innerModelBinder == null) + { + throw new ArgumentNullException(nameof(innerModelBinder)); + } + + _logger = loggerFactory.CreateLogger(); + InnerModelBinder = innerModelBinder; + } + + // to enable unit testing + internal IModelBinder InnerModelBinder { get; } + + /// + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + _logger.AttemptingToBindModel(bindingContext); + + // Property name can be null if the model metadata represents a type (rather than a property or parameter). + 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. + // 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; + if (!request.Headers.ContainsKey(headerName)) + { + _logger.FoundNoValueInRequest(bindingContext); + } + + if (InnerModelBinder == null) + { + BindWithoutInnerBinder(bindingContext); + return; + } + + var headerValueProvider = GetHeaderValueProvider(headerName, bindingContext); + + // Capture the top level object here as entering nested scope would make it 'false'. + var isTopLevelObject = bindingContext.IsTopLevelObject; + + // Create a new binding scope in order to supply the HeaderValueProvider so that the binders like + // SimpleTypeModelBinder can find values from header. + ModelBindingResult result; + using (bindingContext.EnterNestedScope( + bindingContext.ModelMetadata, + fieldName: bindingContext.FieldName, + modelName: bindingContext.ModelName, + model: bindingContext.Model)) + { + bindingContext.IsTopLevelObject = isTopLevelObject; + bindingContext.ValueProvider = headerValueProvider; + + await InnerModelBinder.BindModelAsync(bindingContext); + result = bindingContext.Result; + } + + bindingContext.Result = result; + + _logger.DoneAttemptingToBindModel(bindingContext); + } + + private HeaderValueProvider GetHeaderValueProvider(string headerName, ModelBindingContext bindingContext) + { + var request = bindingContext.HttpContext.Request; + + // Prevent breaking existing users in scenarios where they are binding to a 'string' property + // and expect the whole comma separated string, if any, as a single string and not as a string array. + var values = Array.Empty(); + if (request.Headers.ContainsKey(headerName)) + { + if (bindingContext.ModelMetadata.IsEnumerableType) + { + values = request.Headers.GetCommaSeparatedValues(headerName); + } + else + { + values = new[] { (string)request.Headers[headerName] }; + } + } + + return new HeaderValueProvider(values); + } + + private void BindWithoutInnerBinder(ModelBindingContext bindingContext) + { + var headerName = bindingContext.FieldName; + var request = bindingContext.HttpContext.Request; + + object model; + if (bindingContext.ModelType == typeof(string)) + { + var value = request.Headers[headerName]; + model = (string)value; + } + else if (ModelBindingHelper.CanGetCompatibleCollection(bindingContext)) + { + var values = request.Headers.GetCommaSeparatedValues(headerName); + model = GetCompatibleCollection(bindingContext, values); + } + else + { + // An unsupported datatype or a new collection is needed (perhaps because target type is an array) but + // can't assign it to the property. + model = null; + } + + if (model == null) + { + // Silently fail if unable to create an instance or use the current instance. Also reach here in the + // typeof(string) case if the header does not exist in the request and in the + // typeof(IEnumerable) case if the header does not exist and this is not a top-level object. + bindingContext.Result = ModelBindingResult.Failed(); + } + else + { + bindingContext.ModelState.SetModelValue( + bindingContext.ModelName, + request.Headers.GetCommaSeparatedValues(headerName), + request.Headers[headerName]); + + bindingContext.Result = ModelBindingResult.Success(model); + } + + _logger.DoneAttemptingToBindModel(bindingContext); + } + + private static object GetCompatibleCollection(ModelBindingContext bindingContext, string[] values) + { + // Almost-always success if IsTopLevelObject. + if (!bindingContext.IsTopLevelObject && values.Length == 0) + { + return null; + } + + if (bindingContext.ModelType.IsAssignableFrom(typeof(string[]))) + { + // Array we already have is compatible. + return values; + } + + var collection = ModelBindingHelper.GetCompatibleCollection(bindingContext, values.Length); + for (var i = 0; i < values.Length; i++) + { + collection.Add(values[i]); + } + + return collection; + } + + private class HeaderValueProvider : IValueProvider + { + private readonly string[] _values; + + public HeaderValueProvider(string[] values) + { + Debug.Assert(values != null); + + _values = values; + } + + public bool ContainsPrefix(string prefix) + { + return _values.Length != 0; + } + + public ValueProviderResult GetValue(string key) + { + if (_values.Length == 0) + { + return ValueProviderResult.None; + } + else + { + return new ValueProviderResult(_values, CultureInfo.InvariantCulture); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinderProvider.cs new file mode 100644 index 0000000000..c0f46ff93c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinderProvider.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.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for binding header values. + /// + public class HeaderModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var bindingInfo = context.BindingInfo; + if (bindingInfo.BindingSource == null || + !bindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Header)) + { + return null; + } + + var modelMetadata = context.Metadata; + var loggerFactory = context.Services.GetRequiredService(); + var logger = loggerFactory.CreateLogger(); + + var options = context.Services.GetRequiredService>().Value; + if (!options.AllowBindingHeaderValuesToNonStringModelTypes) + { + if (modelMetadata.ModelType == typeof(string) || + modelMetadata.ElementType == typeof(string)) + { + return new HeaderModelBinder(loggerFactory); + } + else + { + logger.CannotCreateHeaderModelBinderCompatVersion_2_0(modelMetadata.ModelType); + } + + return null; + } + + + if (!IsSimpleType(modelMetadata)) + { + logger.CannotCreateHeaderModelBinder(modelMetadata.ModelType); + return null; + } + + // Since we are delegating the binding of the current model type to other binders, modify the + // binding source of the current model type to a non-FromHeader binding source in order to avoid an + // infinite recursion into this binder provider. + var nestedBindingInfo = new BindingInfo(bindingInfo) + { + BindingSource = BindingSource.ModelBinding + }; + + var innerModelBinder = context.CreateBinder( + modelMetadata.GetMetadataForType(modelMetadata.ModelType), + nestedBindingInfo); + + if (innerModelBinder == null) + { + return null; + } + + return new HeaderModelBinder(loggerFactory, innerModelBinder); + } + + // Support binding only to simple types or collection of simple types. + private bool IsSimpleType(ModelMetadata modelMetadata) + { + var metadata = modelMetadata.ElementMetadata ?? modelMetadata; + return !metadata.IsComplexType; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinder.cs new file mode 100644 index 0000000000..ee90c3f01e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinder.cs @@ -0,0 +1,142 @@ +// Copyright (c) .NET 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.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for . + /// + /// The key type. + /// The value type. + public class KeyValuePairModelBinder : IModelBinder + { + private readonly IModelBinder _keyBinder; + private readonly IModelBinder _valueBinder; + private readonly ILogger _logger; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that also takes an . + /// Creates a new . + /// + /// The for . + /// The for . + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that also takes an " + nameof(ILoggerFactory) + ".")] + public KeyValuePairModelBinder(IModelBinder keyBinder, IModelBinder valueBinder) + : this(keyBinder, valueBinder, NullLoggerFactory.Instance) + { + } + + /// + /// Creates a new . + /// + /// The for . + /// The for . + /// The . + public KeyValuePairModelBinder(IModelBinder keyBinder, IModelBinder valueBinder, ILoggerFactory loggerFactory) + { + if (keyBinder == null) + { + throw new ArgumentNullException(nameof(keyBinder)); + } + + if (valueBinder == null) + { + throw new ArgumentNullException(nameof(valueBinder)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _keyBinder = keyBinder; + _valueBinder = valueBinder; + _logger = loggerFactory.CreateLogger>(); + } + + /// + public async Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + _logger.AttemptingToBindModel(bindingContext); + + var keyModelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "Key"); + var keyResult = await TryBindStrongModel(bindingContext, _keyBinder, "Key", keyModelName); + + var valueModelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, "Value"); + var valueResult = await TryBindStrongModel(bindingContext, _valueBinder, "Value", valueModelName); + + if (keyResult.IsModelSet && valueResult.IsModelSet) + { + var model = new KeyValuePair( + ModelBindingHelper.CastOrDefault(keyResult.Model), + ModelBindingHelper.CastOrDefault(valueResult.Model)); + + bindingContext.Result = ModelBindingResult.Success(model); + _logger.DoneAttemptingToBindModel(bindingContext); + return; + } + + if (!keyResult.IsModelSet && valueResult.IsModelSet) + { + bindingContext.ModelState.TryAddModelError( + keyModelName, + bindingContext.ModelMetadata.ModelBindingMessageProvider.MissingKeyOrValueAccessor()); + _logger.DoneAttemptingToBindModel(bindingContext); + return; + } + + if (keyResult.IsModelSet && !valueResult.IsModelSet) + { + bindingContext.ModelState.TryAddModelError( + valueModelName, + bindingContext.ModelMetadata.ModelBindingMessageProvider.MissingKeyOrValueAccessor()); + _logger.DoneAttemptingToBindModel(bindingContext); + return; + } + + // If we failed to find data for a top-level model, then generate a + // default 'empty' model and return it. + if (bindingContext.IsTopLevelObject) + { + var model = new KeyValuePair(); + bindingContext.Result = ModelBindingResult.Success(model); + } + _logger.DoneAttemptingToBindModel(bindingContext); + } + + internal async Task TryBindStrongModel( + ModelBindingContext bindingContext, + IModelBinder binder, + string propertyName, + string propertyModelName) + { + var propertyModelMetadata = bindingContext.ModelMetadata.Properties[propertyName]; + + using (bindingContext.EnterNestedScope( + modelMetadata: propertyModelMetadata, + fieldName: propertyName, + modelName: propertyModelName, + model: null)) + { + await binder.BindModelAsync(bindingContext); + + return bindingContext.Result; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinderProvider.cs new file mode 100644 index 0000000000..e17f5d0ab9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinderProvider.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET 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 Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for . + /// + public class KeyValuePairModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var modelTypeInfo = context.Metadata.ModelType.GetTypeInfo(); + if (modelTypeInfo.IsGenericType && + modelTypeInfo.GetGenericTypeDefinition().GetTypeInfo() == typeof(KeyValuePair<,>).GetTypeInfo()) + { + var typeArguments = modelTypeInfo.GenericTypeArguments; + + var keyMetadata = context.MetadataProvider.GetMetadataForType(typeArguments[0]); + var keyBinder = context.CreateBinder(keyMetadata); + + var valueMetadata = context.MetadataProvider.GetMetadataForType(typeArguments[1]); + var valueBinder = context.CreateBinder(valueMetadata); + + var binderType = typeof(KeyValuePairModelBinder<,>).MakeGenericType(typeArguments); + var loggerFactory = context.Services.GetRequiredService(); + return (IModelBinder)Activator.CreateInstance(binderType, keyBinder, valueBinder, loggerFactory); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinder.cs new file mode 100644 index 0000000000..97fcefd1fb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinder.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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An which binds models from the request services when a model + /// has the binding source / + /// + public class ServicesModelBinder : IModelBinder + { + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + var requestServices = bindingContext.HttpContext.RequestServices; + var model = requestServices.GetRequiredService(bindingContext.ModelType); + + bindingContext.ValidationState.Add(model, new ValidationStateEntry() { SuppressValidation = true }); + + bindingContext.Result = ModelBindingResult.Success(model); + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinderProvider.cs new file mode 100644 index 0000000000..2dea775a0a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinderProvider.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for binding from the . + /// + public class ServicesModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.BindingInfo.BindingSource != null && + context.BindingInfo.BindingSource.CanAcceptDataFrom(BindingSource.Services)) + { + return new ServicesModelBinder(); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs new file mode 100644 index 0000000000..017049e769 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs @@ -0,0 +1,154 @@ +// Copyright (c) .NET 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.Runtime.ExceptionServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for simple types. + /// + public class SimpleTypeModelBinder : IModelBinder + { + private readonly TypeConverter _typeConverter; + private readonly ILogger _logger; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that also takes an . + /// Initializes a new instance of . + /// + /// The type to create binder for. + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that also takes an " + nameof(ILoggerFactory) + ".")] + public SimpleTypeModelBinder(Type type) + : this(type, NullLoggerFactory.Instance) + { + } + + /// + /// Initializes a new instance of . + /// + /// The type to create binder for. + /// The . + public SimpleTypeModelBinder(Type type, ILoggerFactory loggerFactory) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _typeConverter = TypeDescriptor.GetConverter(type); + _logger = loggerFactory.CreateLogger(); + } + + /// + public Task BindModelAsync(ModelBindingContext bindingContext) + { + if (bindingContext == null) + { + throw new ArgumentNullException(nameof(bindingContext)); + } + + var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + if (valueProviderResult == ValueProviderResult.None) + { + _logger.FoundNoValueInRequest(bindingContext); + + // no entry + _logger.DoneAttemptingToBindModel(bindingContext); + return Task.CompletedTask; + } + + _logger.AttemptingToBindModel(bindingContext); + + bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult); + + try + { + var value = valueProviderResult.FirstValue; + + object model; + if (bindingContext.ModelType == typeof(string)) + { + // Already have a string. No further conversion required but handle ConvertEmptyStringToNull. + if (bindingContext.ModelMetadata.ConvertEmptyStringToNull && string.IsNullOrWhiteSpace(value)) + { + model = null; + } + else + { + model = value; + } + } + else if (string.IsNullOrWhiteSpace(value)) + { + // Other than the StringConverter, converters Trim() the value then throw if the result is empty. + model = null; + } + else + { + model = _typeConverter.ConvertFrom( + context: null, + culture: valueProviderResult.Culture, + value: value); + } + + CheckModel(bindingContext, valueProviderResult, model); + + _logger.DoneAttemptingToBindModel(bindingContext); + return Task.CompletedTask; + } + catch (Exception exception) + { + var isFormatException = exception is FormatException; + if (!isFormatException && exception.InnerException != null) + { + // TypeConverter throws System.Exception wrapping the FormatException, + // so we capture the inner exception. + exception = ExceptionDispatchInfo.Capture(exception.InnerException).SourceException; + } + + bindingContext.ModelState.TryAddModelError( + bindingContext.ModelName, + exception, + bindingContext.ModelMetadata); + + // Were able to find a converter for the type but conversion failed. + return Task.CompletedTask; + } + } + + protected virtual void CheckModel( + ModelBindingContext bindingContext, + ValueProviderResult valueProviderResult, + object model) + { + // When converting newModel a null value may indicate a failed conversion for an otherwise required + // model (can't set a ValueType to null). This detects if a null model value is acceptable given the + // current bindingContext. If not, an error is logged. + if (model == null && !bindingContext.ModelMetadata.IsReferenceOrNullableType) + { + bindingContext.ModelState.TryAddModelError( + bindingContext.ModelName, + bindingContext.ModelMetadata.ModelBindingMessageProvider.ValueMustNotBeNullAccessor( + valueProviderResult.ToString())); + } + else + { + bindingContext.Result = ModelBindingResult.Success(model); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinderProvider.cs new file mode 100644 index 0000000000..f7d69e016c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinderProvider.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; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders +{ + /// + /// An for binding simple data types. + /// + public class SimpleTypeModelBinderProvider : IModelBinderProvider + { + /// + public IModelBinder GetBinder(ModelBinderProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (!context.Metadata.IsComplexType) + { + var loggerFactory = context.Services.GetRequiredService(); + return new SimpleTypeModelBinder(context.Metadata.ModelType, loggerFactory); + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehavior.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehavior.cs new file mode 100644 index 0000000000..1a9bde1fb8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehavior.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Enumerates behavior options of the model binding system. + /// + public enum BindingBehavior + { + /// + /// The property should be model bound if a value is available from the value provider. + /// + Optional = 0, + + /// + /// The property should be excluded from model binding. + /// + Never, + + /// + /// The property is required for model binding. + /// + Required + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehaviorAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehaviorAttribute.cs new file mode 100644 index 0000000000..b27c8451d4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehaviorAttribute.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 System; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Specifies the that should be applied. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] + public class BindingBehaviorAttribute : Attribute + { + /// + /// Initializes a new instance. + /// + /// The to apply. + public BindingBehaviorAttribute(BindingBehavior behavior) + { + Behavior = behavior; + } + + /// + /// Gets the to apply. + /// + public BindingBehavior Behavior { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingSourceValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingSourceValueProvider.cs new file mode 100644 index 0000000000..2cb4198ae5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingSourceValueProvider.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 Microsoft.AspNetCore.Mvc.Core; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A value provider which provides data from a specific . + /// + /// + /// + /// A is an base-implementation which + /// can provide data for all parameters and model properties which specify the corresponding + /// . + /// + /// + /// implements and will + /// include or exclude itself from the set of value providers based on the model's associated + /// . Value providers are by-default included; if a model does not + /// specify a then all value providers are valid. + /// + /// + public abstract class BindingSourceValueProvider : IBindingSourceValueProvider + { + /// + /// Creates a new . + /// + /// + /// The . Must be a single-source (non-composite) with + /// equal to false. + /// + public BindingSourceValueProvider(BindingSource bindingSource) + { + if (bindingSource == null) + { + throw new ArgumentNullException(nameof(bindingSource)); + } + + if (bindingSource.IsGreedy) + { + var message = Resources.FormatBindingSource_CannotBeGreedy( + bindingSource.DisplayName, + nameof(BindingSourceValueProvider)); + throw new ArgumentException(message, nameof(bindingSource)); + } + + if (bindingSource is CompositeBindingSource) + { + var message = Resources.FormatBindingSource_CannotBeComposite( + bindingSource.DisplayName, + nameof(BindingSourceValueProvider)); + throw new ArgumentException(message, nameof(bindingSource)); + } + + BindingSource = bindingSource; + } + + /// + /// Gets the corresponding . + /// + protected BindingSource BindingSource { get; } + + /// + public abstract bool ContainsPrefix(string prefix); + + /// + public abstract ValueProviderResult GetValue(string key); + + /// + public virtual IValueProvider Filter(BindingSource bindingSource) + { + if (bindingSource == null) + { + throw new ArgumentNullException(nameof(bindingSource)); + } + + if (bindingSource.CanAcceptDataFrom(BindingSource)) + { + return this; + } + else + { + return null; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/CompositeValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/CompositeValueProvider.cs new file mode 100644 index 0000000000..eaf15df1be --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/CompositeValueProvider.cs @@ -0,0 +1,254 @@ +// Copyright (c) .NET 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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Represents a whose values come from a collection of s. + /// + public class CompositeValueProvider : + Collection, + IEnumerableValueProvider, + IBindingSourceValueProvider, + IKeyRewriterValueProvider + { + /// + /// Initializes a new instance of . + /// + public CompositeValueProvider() + { + } + + /// + /// Initializes a new instance of . + /// + /// The sequence of to add to this instance of + /// . + public CompositeValueProvider(IList valueProviders) + : base(valueProviders) + { + } + + /// + /// Asynchronously creates a using the provided + /// . + /// + /// The associated with the current request. + /// + /// A which, when completed, asynchronously returns a + /// . + /// + public static async Task CreateAsync(ControllerContext controllerContext) + { + if (controllerContext == null) + { + throw new ArgumentNullException(nameof(controllerContext)); + } + + var factories = controllerContext.ValueProviderFactories; + + return await CreateAsync(controllerContext, factories); + } + + /// + /// Asynchronously creates a using the provided + /// . + /// + /// The associated with the current request. + /// The to be applied to the context. + /// + /// A which, when completed, asynchronously returns a + /// . + /// + public static async Task CreateAsync( + ActionContext actionContext, + IList factories) + { + var valueProviderFactoryContext = new ValueProviderFactoryContext(actionContext); + + for (var i = 0; i < factories.Count; i++) + { + var factory = factories[i]; + await factory.CreateValueProviderAsync(valueProviderFactoryContext); + } + + return new CompositeValueProvider(valueProviderFactoryContext.ValueProviders); + } + + /// + public virtual bool ContainsPrefix(string prefix) + { + for (var i = 0; i < Count; i++) + { + if (this[i].ContainsPrefix(prefix)) + { + return true; + } + } + return false; + } + + /// + public virtual ValueProviderResult GetValue(string key) + { + // Performance-sensitive + // Caching the count is faster for IList + var itemCount = Items.Count; + for (var i = 0; i < itemCount; i++) + { + var valueProvider = Items[i]; + var result = valueProvider.GetValue(key); + if (result != ValueProviderResult.None) + { + return result; + } + } + + return ValueProviderResult.None; + } + + /// + public virtual IDictionary GetKeysFromPrefix(string prefix) + { + foreach (var valueProvider in this) + { + if (valueProvider is IEnumerableValueProvider enumeratedProvider) + { + var result = enumeratedProvider.GetKeysFromPrefix(prefix); + if (result != null && result.Count > 0) + { + return result; + } + } + } + return new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + protected override void InsertItem(int index, IValueProvider item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + base.InsertItem(index, item); + } + + /// + protected override void SetItem(int index, IValueProvider item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + base.SetItem(index, item); + } + + /// + public IValueProvider Filter(BindingSource bindingSource) + { + if (bindingSource == null) + { + throw new ArgumentNullException(nameof(bindingSource)); + } + + var shouldFilter = false; + for (var i = 0; i < Count; i++) + { + var valueProvider = Items[i]; + if (valueProvider is IBindingSourceValueProvider) + { + shouldFilter = true; + break; + } + } + + if (!shouldFilter) + { + // No inner IBindingSourceValueProvider implementations. Result will be empty. + return null; + } + + var filteredValueProviders = new List(); + for (var i = 0; i < Count; i++) + { + var valueProvider = Items[i]; + if (valueProvider is IBindingSourceValueProvider bindingSourceValueProvider) + { + var result = bindingSourceValueProvider.Filter(bindingSource); + if (result != null) + { + filteredValueProviders.Add(result); + } + } + } + + if (filteredValueProviders.Count == 0) + { + // Do not create an empty CompositeValueProvider. + return null; + } + + return new CompositeValueProvider(filteredValueProviders); + } + + /// + /// + /// Value providers are included by default. If a contained does not implement + /// , will not remove it. + /// + public IValueProvider Filter() + { + var shouldFilter = false; + for (var i = 0; i < Count; i++) + { + var valueProvider = Items[i]; + if (valueProvider is IKeyRewriterValueProvider) + { + shouldFilter = true; + break; + } + } + + if (!shouldFilter) + { + // No inner IKeyRewriterValueProvider implementations. Nothing to exclude. + return this; + } + + var filteredValueProviders = new List(); + for (var i = 0; i < Count; i++) + { + var valueProvider = Items[i]; + if (valueProvider is IKeyRewriterValueProvider keyRewriterValueProvider) + { + var result = keyRewriterValueProvider.Filter(); + if (result != null) + { + filteredValueProviders.Add(result); + } + } + else + { + // Assume value providers that aren't rewriter-aware do not rewrite their keys. + filteredValueProviders.Add(valueProvider); + } + } + + if (filteredValueProviders.Count == 0) + { + // Do not create an empty CompositeValueProvider. + return null; + } + + return new CompositeValueProvider(filteredValueProviders); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/DefaultPropertyFilterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/DefaultPropertyFilterProvider.cs new file mode 100644 index 0000000000..3d1bc81a71 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/DefaultPropertyFilterProvider.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.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Default implementation for . + /// Provides a expression based way to provide include properties. + /// + /// The target model Type. + public class DefaultPropertyFilterProvider : IPropertyFilterProvider + where TModel : class + { + private static readonly Func _default = (m) => true; + + /// + /// The prefix which is used while generating the property filter. + /// + public virtual string Prefix => string.Empty; + + /// + /// Expressions which can be used to generate property filter which can filter model + /// properties. + /// + public virtual IEnumerable>> PropertyIncludeExpressions => null; + + /// + public virtual Func PropertyFilter + { + get + { + if (PropertyIncludeExpressions == null) + { + return _default; + } + + // We do not cache by default. + return GetPropertyFilterFromExpression(PropertyIncludeExpressions); + } + } + + private Func GetPropertyFilterFromExpression( + IEnumerable>> includeExpressions) + { + var expression = ModelBindingHelper.GetPropertyFilterExpression(includeExpressions.ToArray()); + return expression.Compile(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs new file mode 100644 index 0000000000..122d760af8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.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; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + public class EmptyModelMetadataProvider : DefaultModelMetadataProvider + { + public EmptyModelMetadataProvider() + : base( + new DefaultCompositeMetadataDetailsProvider(new List()), + new OptionsAccessor()) + { + } + + private class OptionsAccessor : IOptions + { + public MvcOptions Value { get; } = new MvcOptions(); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProvider.cs new file mode 100644 index 0000000000..0e347e99d7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProvider.cs @@ -0,0 +1,98 @@ +// Copyright (c) .NET 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.Http; +using Microsoft.AspNetCore.Mvc.Internal; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An adapter for data stored in an . + /// + public class FormValueProvider : BindingSourceValueProvider, IEnumerableValueProvider + { + private readonly CultureInfo _culture; + private readonly IFormCollection _values; + private PrefixContainer _prefixContainer; + + /// + /// Creates a value provider for . + /// + /// The for the data. + /// The key value pairs to wrap. + /// The culture to return with ValueProviderResult instances. + public FormValueProvider( + BindingSource bindingSource, + IFormCollection values, + CultureInfo culture) + : base(bindingSource) + { + if (bindingSource == null) + { + throw new ArgumentNullException(nameof(bindingSource)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + _values = values; + _culture = culture; + } + + public CultureInfo Culture => _culture; + + protected PrefixContainer PrefixContainer + { + get + { + if (_prefixContainer == null) + { + _prefixContainer = new PrefixContainer(_values.Keys); + } + + return _prefixContainer; + } + } + + /// + public override bool ContainsPrefix(string prefix) + { + return PrefixContainer.ContainsPrefix(prefix); + } + + /// + public virtual IDictionary GetKeysFromPrefix(string prefix) + { + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + return PrefixContainer.GetKeysFromPrefix(prefix); + } + + /// + public override ValueProviderResult GetValue(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + var values = _values[key]; + if (values.Count == 0) + { + return ValueProviderResult.None; + } + else + { + return new ValueProviderResult(values, _culture); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProviderFactory.cs new file mode 100644 index 0000000000..f9febdef06 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProviderFactory.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 System.Globalization; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A for . + /// + public class FormValueProviderFactory : IValueProviderFactory + { + /// + public Task CreateValueProviderAsync(ValueProviderFactoryContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var request = context.ActionContext.HttpContext.Request; + if (request.HasFormContentType) + { + // Allocating a Task only when the body is form data. + return AddValueProviderAsync(context); + } + + return Task.CompletedTask; + } + + private static async Task AddValueProviderAsync(ValueProviderFactoryContext context) + { + var request = context.ActionContext.HttpContext.Request; + var valueProvider = new FormValueProvider( + BindingSource.Form, + await request.ReadFormAsync(), + CultureInfo.CurrentCulture); + + context.ValueProviders.Add(valueProvider); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IBindingSourceValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IBindingSourceValueProvider.cs new file mode 100644 index 0000000000..dcd948fd29 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IBindingSourceValueProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A value provider which can filter its contents based on . + /// + /// + /// Value providers are by-default included. If a model does not specify a + /// then all value providers are valid. + /// + public interface IBindingSourceValueProvider : IValueProvider + { + /// + /// Filters the value provider based on . + /// + /// The associated with a model. + /// + /// The filtered value provider, or null if the value provider does not match + /// . + /// + IValueProvider Filter(BindingSource bindingSource); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ICollectionModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ICollectionModelBinder.cs new file mode 100644 index 0000000000..b3c5781e3e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ICollectionModelBinder.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.ModelBinding +{ + /// + /// Interface for model binding collections. + /// + public interface ICollectionModelBinder : IModelBinder + { + /// + /// Gets an indication whether or not this implementation can create + /// an assignable to . + /// + /// of the model. + /// + /// true if this implementation can create an + /// assignable to ; false otherwise. + /// + /// + /// A true return value is necessary for successful model binding if model is initially null. + /// + bool CanCreateInstance(Type targetType); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IEnumerableValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IEnumerableValueProvider.cs new file mode 100644 index 0000000000..2e1a1cb82c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IEnumerableValueProvider.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.ModelBinding +{ + public interface IEnumerableValueProvider : IValueProvider + { + IDictionary GetKeysFromPrefix(string prefix); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IKeyRewriterValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IKeyRewriterValueProvider.cs new file mode 100644 index 0000000000..f2bb03cdb7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IKeyRewriterValueProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A value provider which can filter its contents to remove keys rewritten compared to the request data. + /// + public interface IKeyRewriterValueProvider : IValueProvider + { + /// + /// Filters the value provider to remove keys rewritten compared to the request data. + /// + /// + /// If the request contains values with keys Model.Property and Collection[index], the returned + /// will not match Model[Property] or Collection.index. + /// + /// + /// The filtered value provider or if the value provider only contains rewritten keys. + /// + IValueProvider Filter(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IModelBinderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IModelBinderFactory.cs new file mode 100644 index 0000000000..310183424a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IModelBinderFactory.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.ModelBinding +{ + /// + /// A factory abstraction for creating instances. + /// + public interface IModelBinderFactory + { + /// + /// Creates a new . + /// + /// The . + /// An instance. + IModelBinder CreateBinder(ModelBinderFactoryContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ModelBindingHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ModelBindingHelper.cs new file mode 100644 index 0000000000..90d8dd79f1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ModelBindingHelper.cs @@ -0,0 +1,803 @@ +// Copyright (c) .NET 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.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Runtime.ExceptionServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Internal +{ + public static class ModelBindingHelper + { + /// + /// Updates the specified instance using the specified + /// and the specified and executes + /// validation using the specified . + /// + /// The type of the model object. + /// The model instance to update and validate. + /// The prefix to use when looking up values in the . + /// + /// The for the current executing request. + /// The provider used for reading metadata for the model type. + /// The used for binding. + /// The used for looking up values. + /// The used for validating the + /// bound values. + /// A that on completion returns true if the update is successful + public static Task TryUpdateModelAsync( + TModel model, + string prefix, + ActionContext actionContext, + IModelMetadataProvider metadataProvider, + IModelBinderFactory modelBinderFactory, + IValueProvider valueProvider, + IObjectModelValidator objectModelValidator) + where TModel : class + { + return TryUpdateModelAsync( + model, + prefix, + actionContext, + metadataProvider, + modelBinderFactory, + valueProvider, + objectModelValidator, + // Includes everything by default. + propertyFilter: (m) => true); + } + + /// + /// Updates the specified instance using the specified + /// and the specified and executes validation using the specified + /// . + /// + /// The type of the model object. + /// The model instance to update and validate. + /// The prefix to use when looking up values in the . + /// + /// The for the current executing request. + /// The provider used for reading metadata for the model type. + /// The used for binding. + /// The used for looking up values. + /// The used for validating the + /// bound values. + /// Expression(s) which represent top level properties + /// which need to be included for the current model. + /// A that on completion returns true if the update is successful + public static Task TryUpdateModelAsync( + TModel model, + string prefix, + ActionContext actionContext, + IModelMetadataProvider metadataProvider, + IModelBinderFactory modelBinderFactory, + IValueProvider valueProvider, + IObjectModelValidator objectModelValidator, + params Expression>[] includeExpressions) + where TModel : class + { + if (includeExpressions == null) + { + throw new ArgumentNullException(nameof(includeExpressions)); + } + + var expression = GetPropertyFilterExpression(includeExpressions); + var propertyFilter = expression.Compile(); + + return TryUpdateModelAsync( + model, + prefix, + actionContext, + metadataProvider, + modelBinderFactory, + valueProvider, + objectModelValidator, + propertyFilter); + } + + /// + /// Updates the specified instance using the specified + /// and the specified and executes validation using the specified + /// . + /// + /// The type of the model object. + /// The model instance to update and validate. + /// The prefix to use when looking up values in the . + /// + /// The for the current executing request. + /// The provider used for reading metadata for the model type. + /// The used for binding. + /// The used for looking up values. + /// The used for validating the + /// bound values. + /// + /// A predicate which can be used to filter properties(for inclusion/exclusion) at runtime. + /// + /// A that on completion returns true if the update is successful + public static Task TryUpdateModelAsync( + TModel model, + string prefix, + ActionContext actionContext, + IModelMetadataProvider metadataProvider, + IModelBinderFactory modelBinderFactory, + IValueProvider valueProvider, + IObjectModelValidator objectModelValidator, + Func propertyFilter) + where TModel : class + { + return TryUpdateModelAsync( + model, + typeof(TModel), + prefix, + actionContext, + metadataProvider, + modelBinderFactory, + valueProvider, + objectModelValidator, + propertyFilter); + } + + /// + /// Updates the specified instance using the specified + /// and the specified and executes validation using the specified + /// . + /// + /// The model instance to update and validate. + /// The type of model instance to update and validate. + /// The prefix to use when looking up values in the . + /// + /// The for the current executing request. + /// The provider used for reading metadata for the model type. + /// The used for binding. + /// The used for looking up values. + /// The used for validating the + /// bound values. + /// A that on completion returns true if the update is successful + public static Task TryUpdateModelAsync( + object model, + Type modelType, + string prefix, + ActionContext actionContext, + IModelMetadataProvider metadataProvider, + IModelBinderFactory modelBinderFactory, + IValueProvider valueProvider, + IObjectModelValidator objectModelValidator) + { + return TryUpdateModelAsync( + model, + modelType, + prefix, + actionContext, + metadataProvider, + modelBinderFactory, + valueProvider, + objectModelValidator, + // Includes everything by default. + propertyFilter: (m) => true); + } + + /// + /// Updates the specified instance using the specified + /// and the specified and executes validation using the specified + /// . + /// + /// The model instance to update and validate. + /// The type of model instance to update and validate. + /// The prefix to use when looking up values in the . + /// + /// The for the current executing request. + /// The provider used for reading metadata for the model type. + /// The used for binding. + /// The used for looking up values. + /// The used for validating the + /// bound values. + /// A predicate which can be used to + /// filter properties(for inclusion/exclusion) at runtime. + /// A that on completion returns true if the update is successful + public static async Task TryUpdateModelAsync( + object model, + Type modelType, + string prefix, + ActionContext actionContext, + IModelMetadataProvider metadataProvider, + IModelBinderFactory modelBinderFactory, + IValueProvider valueProvider, + IObjectModelValidator objectModelValidator, + Func propertyFilter) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + if (metadataProvider == null) + { + throw new ArgumentNullException(nameof(metadataProvider)); + } + + if (modelBinderFactory == null) + { + throw new ArgumentNullException(nameof(modelBinderFactory)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (objectModelValidator == null) + { + throw new ArgumentNullException(nameof(objectModelValidator)); + } + + if (propertyFilter == null) + { + throw new ArgumentNullException(nameof(propertyFilter)); + } + + if (!modelType.IsAssignableFrom(model.GetType())) + { + var message = Resources.FormatModelType_WrongType( + model.GetType().FullName, + modelType.FullName); + throw new ArgumentException(message, nameof(modelType)); + } + + var modelMetadata = metadataProvider.GetMetadataForType(modelType); + var modelState = actionContext.ModelState; + + var modelBindingContext = DefaultModelBindingContext.CreateBindingContext( + actionContext, + valueProvider, + modelMetadata, + bindingInfo: null, + modelName: prefix); + + modelBindingContext.Model = model; + modelBindingContext.PropertyFilter = propertyFilter; + + var factoryContext = new ModelBinderFactoryContext() + { + Metadata = modelMetadata, + BindingInfo = new BindingInfo() + { + BinderModelName = modelMetadata.BinderModelName, + BinderType = modelMetadata.BinderType, + BindingSource = modelMetadata.BindingSource, + PropertyFilterProvider = modelMetadata.PropertyFilterProvider, + }, + + // We're using the model metadata as the cache token here so that TryUpdateModelAsync calls + // for the same model type can share a binder. This won't overlap with normal model binding + // operations because they use the ParameterDescriptor for the token. + CacheToken = modelMetadata, + }; + var binder = modelBinderFactory.CreateBinder(factoryContext); + + await binder.BindModelAsync(modelBindingContext); + var modelBindingResult = modelBindingContext.Result; + if (modelBindingResult.IsModelSet) + { + objectModelValidator.Validate( + actionContext, + modelBindingContext.ValidationState, + modelBindingContext.ModelName, + modelBindingResult.Model); + + return modelState.IsValid; + } + + return false; + } + + // Internal for tests + internal static string GetPropertyName(Expression expression) + { + if (expression.NodeType == ExpressionType.Convert || + expression.NodeType == ExpressionType.ConvertChecked) + { + // For Boxed Value Types + expression = ((UnaryExpression)expression).Operand; + } + + if (expression.NodeType != ExpressionType.MemberAccess) + { + throw new InvalidOperationException( + Resources.FormatInvalid_IncludePropertyExpression(expression.NodeType)); + } + + var memberExpression = (MemberExpression)expression; + if (memberExpression.Member is PropertyInfo memberInfo) + { + if (memberExpression.Expression.NodeType != ExpressionType.Parameter) + { + // Chained expressions and non parameter based expressions are not supported. + throw new InvalidOperationException( + Resources.FormatInvalid_IncludePropertyExpression(expression.NodeType)); + } + + return memberInfo.Name; + } + else + { + // Fields are also not supported. + throw new InvalidOperationException( + Resources.FormatInvalid_IncludePropertyExpression(expression.NodeType)); + } + } + + /// + /// Creates an expression for a predicate to limit the set of properties used in model binding. + /// + /// The model type. + /// Expressions identifying the properties to allow for binding. + /// An expression which can be used with . + public static Expression> GetPropertyFilterExpression( + Expression>[] expressions) + { + if (expressions.Length == 0) + { + // If nothing is included explicitly, treat everything as included. + return (m) => true; + } + + var firstExpression = GetPredicateExpression(expressions[0]); + var orWrapperExpression = firstExpression.Body; + foreach (var expression in expressions.Skip(1)) + { + var predicate = GetPredicateExpression(expression); + orWrapperExpression = Expression.OrElse( + orWrapperExpression, + Expression.Invoke(predicate, firstExpression.Parameters)); + } + + return Expression.Lambda>(orWrapperExpression, firstExpression.Parameters); + } + + private static Expression> GetPredicateExpression( + Expression> expression) + { + var propertyName = GetPropertyName(expression.Body); + + return (metadata) => string.Equals(metadata.PropertyName, propertyName, StringComparison.Ordinal); + } + + /// + /// Clears entries for . + /// + /// The of the model. + /// The associated with the model. + /// The . + /// The entry to clear. + public static void ClearValidationStateForModel( + Type modelType, + ModelStateDictionary modelState, + IModelMetadataProvider metadataProvider, + string modelKey) + { + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + if (metadataProvider == null) + { + throw new ArgumentNullException(nameof(metadataProvider)); + } + + ClearValidationStateForModel(metadataProvider.GetMetadataForType(modelType), modelState, modelKey); + } + + /// + /// Clears entries for . + /// + /// The . + /// The associated with the model. + /// The entry to clear. + public static void ClearValidationStateForModel( + ModelMetadata modelMetadata, + ModelStateDictionary modelState, + string modelKey) + { + if (modelMetadata == null) + { + throw new ArgumentNullException(nameof(modelMetadata)); + } + + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + if (string.IsNullOrEmpty(modelKey)) + { + // If model key is empty, we have to do a best guess to try and clear the appropriate + // keys. Clearing the empty prefix would clear the state of ALL entries, which might wipe out + // data from other models. + if (modelMetadata.IsEnumerableType) + { + // We expect that any key beginning with '[' is an index. We can't just infer the indexes + // used, so we clear all keys that look like index>. + // + // In the unlikely case that multiple top-level collections where bound to the empty prefix, + // you're just out of luck. + foreach (var kvp in modelState) + { + if (kvp.Key.Length > 0 && kvp.Key[0] == '[') + { + // Starts with an indexer + kvp.Value.Errors.Clear(); + kvp.Value.ValidationState = ModelValidationState.Unvalidated; + } + } + } + else if (modelMetadata.IsComplexType) + { + for (var i = 0; i < modelMetadata.Properties.Count; i++) + { + var property = modelMetadata.Properties[i]; + modelState.ClearValidationState(property.BinderModelName ?? property.PropertyName); + } + } + else + { + // Simple types bind to a single entry. So clear the entry with the empty-key, in the + // unlikely event that it has errors. + var entry = modelState[string.Empty]; + if (entry != null) + { + entry.Errors.Clear(); + entry.ValidationState = ModelValidationState.Unvalidated; + } + } + } + else + { + // If model key is non-empty, we just want to clear all keys with that prefix. We expect + // model binding to have only used this key (and suffixes) for all entries related to + // this model. + modelState.ClearValidationState(modelKey); + } + } + + internal static TModel CastOrDefault(object model) + { + return (model is TModel) ? (TModel)model : default(TModel); + } + + /// + /// Gets an indication whether is likely to return a usable + /// non-null value. + /// + /// The element type of the required. + /// The . + /// + /// true if is likely to return a usable non-null + /// value; false otherwise. + /// + /// "Usable" in this context means the property can be set or its value reused. + public static bool CanGetCompatibleCollection(ModelBindingContext bindingContext) + { + var model = bindingContext.Model; + var modelType = bindingContext.ModelType; + + if (typeof(T).IsAssignableFrom(modelType)) + { + // Scalar case. Existing model is not relevant and property must always be set. Will use a List + // intermediate and set property to first element, if any. + return true; + } + + if (modelType == typeof(T[])) + { + // Can't change the length of an existing array or replace it. Will use a List intermediate and set + // property to an array created from that. + return true; + } + + if (!typeof(IEnumerable).IsAssignableFrom(modelType)) + { + // Not a supported collection. + return false; + } + + if (model is ICollection collection && !collection.IsReadOnly) + { + // Can use the existing collection. + return true; + } + + // Most likely the model is null. + // Also covers the corner case where the model implements IEnumerable but not ICollection e.g. + // public IEnumerable Property { get; set; } = new T[0]; + if (modelType.IsAssignableFrom(typeof(List))) + { + return true; + } + + // Will we be able to activate an instance and use that? + return modelType.GetTypeInfo().IsClass && + !modelType.GetTypeInfo().IsAbstract && + typeof(ICollection).IsAssignableFrom(modelType); + } + + /// + /// Creates an instance compatible with 's + /// . + /// + /// The element type of the required. + /// The . + /// + /// An instance compatible with 's + /// . + /// + /// + /// Should not be called if returned false. + /// + public static ICollection GetCompatibleCollection(ModelBindingContext bindingContext) + { + return GetCompatibleCollection(bindingContext, capacity: null); + } + + /// + /// Creates an instance compatible with 's + /// . + /// + /// The element type of the required. + /// The . + /// + /// Capacity for use when creating a instance. Not used when creating another type. + /// + /// + /// An instance compatible with 's + /// . + /// + /// + /// Should not be called if returned false. + /// + public static ICollection GetCompatibleCollection(ModelBindingContext bindingContext, int capacity) + { + return GetCompatibleCollection(bindingContext, (int?)capacity); + } + + private static ICollection GetCompatibleCollection(ModelBindingContext bindingContext, int? capacity) + { + var model = bindingContext.Model; + var modelType = bindingContext.ModelType; + + // There's a limited set of collection types we can create here. + // + // For the simple cases: Choose List if the destination type supports it (at least as an intermediary). + // + // For more complex cases: If the destination type is a class that implements ICollection, then activate + // an instance and return that. + // + // Otherwise just give up. + if (typeof(T).IsAssignableFrom(modelType)) + { + return CreateList(capacity); + } + + if (modelType == typeof(T[])) + { + return CreateList(capacity); + } + + // Does collection exist and can it be reused? + if (model is ICollection collection && !collection.IsReadOnly) + { + collection.Clear(); + + return collection; + } + + if (modelType.IsAssignableFrom(typeof(List))) + { + return CreateList(capacity); + } + + return (ICollection)Activator.CreateInstance(modelType); + } + + private static List CreateList(int? capacity) + { + return capacity.HasValue ? new List(capacity.Value) : new List(); + } + + /// + /// Converts the provided to a value of . + /// + /// The for conversion. + /// The value to convert."/> + /// The for conversion. + /// + /// The converted value or the default value of if the value could not be converted. + /// + public static T ConvertTo(object value, CultureInfo culture) + { + var converted = ConvertTo(value, typeof(T), culture); + return converted == null ? default(T) : (T)converted; + } + + /// + /// Converts the provided to a value of . + /// + /// The value to convert."/> + /// The for conversion. + /// The for conversion. + /// + /// The converted value or null if the value could not be converted. + /// + public static object ConvertTo(object value, Type type, CultureInfo culture) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (value == null) + { + // For value types, treat null values as though they were the default value for the type. + return type.GetTypeInfo().IsValueType ? Activator.CreateInstance(type) : null; + } + + if (type.IsAssignableFrom(value.GetType())) + { + return value; + } + + var cultureToUse = culture ?? CultureInfo.InvariantCulture; + return UnwrapPossibleArrayType(value, type, cultureToUse); + } + + private static object UnwrapPossibleArrayType(object value, Type destinationType, CultureInfo culture) + { + // array conversion results in four cases, as below + var valueAsArray = value as Array; + if (destinationType.IsArray) + { + var destinationElementType = destinationType.GetElementType(); + if (valueAsArray != null) + { + // case 1: both destination + source type are arrays, so convert each element + var converted = (IList)Array.CreateInstance(destinationElementType, valueAsArray.Length); + for (var i = 0; i < valueAsArray.Length; i++) + { + converted[i] = ConvertSimpleType(valueAsArray.GetValue(i), destinationElementType, culture); + } + return converted; + } + else + { + // case 2: destination type is array but source is single element, so wrap element in + // array + convert + var element = ConvertSimpleType(value, destinationElementType, culture); + var converted = (IList)Array.CreateInstance(destinationElementType, 1); + converted[0] = element; + return converted; + } + } + else if (valueAsArray != null) + { + // case 3: destination type is single element but source is array, so extract first element + convert + if (valueAsArray.Length > 0) + { + value = valueAsArray.GetValue(0); + return ConvertSimpleType(value, destinationType, culture); + } + else + { + // case 3(a): source is empty array, so can't perform conversion + return null; + } + } + + // case 4: both destination + source type are single elements, so convert + return ConvertSimpleType(value, destinationType, culture); + } + + private static object ConvertSimpleType(object value, Type destinationType, CultureInfo culture) + { + if (value == null || destinationType.IsAssignableFrom(value.GetType())) + { + return value; + } + + // In case of a Nullable object, we try again with its underlying type. + destinationType = UnwrapNullableType(destinationType); + + // if this is a user-input value but the user didn't type anything, return no value + if (value is string valueAsString && string.IsNullOrWhiteSpace(valueAsString)) + { + return null; + } + + var converter = TypeDescriptor.GetConverter(destinationType); + var canConvertFrom = converter.CanConvertFrom(value.GetType()); + if (!canConvertFrom) + { + converter = TypeDescriptor.GetConverter(value.GetType()); + } + if (!(canConvertFrom || converter.CanConvertTo(destinationType))) + { + // EnumConverter cannot convert integer, so we verify manually + if (destinationType.GetTypeInfo().IsEnum && + (value is int || + value is uint || + value is long || + value is ulong || + value is short || + value is ushort || + value is byte || + value is sbyte)) + { + return Enum.ToObject(destinationType, value); + } + + throw new InvalidOperationException( + Resources.FormatValueProviderResult_NoConverterExists(value.GetType(), destinationType)); + } + + try + { + return canConvertFrom + ? converter.ConvertFrom(null, culture, value) + : converter.ConvertTo(null, culture, value, destinationType); + } + catch (FormatException) + { + throw; + } + catch (Exception ex) + { + if (ex.InnerException == null) + { + throw; + } + else + { + // TypeConverter throws System.Exception wrapping the FormatException, + // so we throw the inner exception. + ExceptionDispatchInfo.Capture(ex.InnerException).Throw(); + + // This code is never reached because the previous line will always throw. + throw; + } + } + } + + private static Type UnwrapNullableType(Type destinationType) + { + return Nullable.GetUnderlyingType(destinationType) ?? destinationType; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ValidationStack.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ValidationStack.cs new file mode 100644 index 0000000000..ba24828544 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ValidationStack.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 System.Collections.Generic; +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc.Internal; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Internal +{ + public class ValidationStack + { + public int Count => HashSet?.Count ?? List.Count; + + // We tested the performance of a list at size 15 and found it still better than hashset, but to avoid a costly + // O(n) search at larger n we set the cutoff to 20. If someone finds the point where they intersect feel free to change this number. + internal const int CutOff = 20; + + internal List List { get; } = new List(); + + internal HashSet HashSet { get; set; } + + public bool Push(object model) + { + if (HashSet != null) + { + return HashSet.Add(model); + } + + if (ListContains(model)) + { + return false; + } + + List.Add(model); + + if (HashSet == null && List.Count > CutOff) + { + HashSet = new HashSet(List, ReferenceEqualityComparer.Instance); + } + + return true; + } + + public void Pop(object model) + { + if (HashSet != null) + { + HashSet.Remove(model); + } + else + { + if (model != null) + { + Debug.Assert(ReferenceEquals(List[List.Count - 1], model)); + List.RemoveAt(List.Count - 1); + } + } + } + + private bool ListContains(object model) + { + for (var i = 0; i < List.Count; i++) + { + if (ReferenceEquals(model, List[i])) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProvider.cs new file mode 100644 index 0000000000..db1706d964 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProvider.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.Collections.Generic; +using System.Globalization; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An for jQuery formatted form data. + /// + public class JQueryFormValueProvider : JQueryValueProvider + { + /// + /// Initializes a new instance of the class. + /// + /// The of the data. + /// The values. + /// The culture to return with ValueProviderResult instances. + public JQueryFormValueProvider( + BindingSource bindingSource, + IDictionary values, + CultureInfo culture) + : base(bindingSource, values, culture) + { + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProviderFactory.cs new file mode 100644 index 0000000000..fdd8eb2d93 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProviderFactory.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; +using System.Globalization; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An for . + /// + public class JQueryFormValueProviderFactory : IValueProviderFactory + { + /// + public Task CreateValueProviderAsync(ValueProviderFactoryContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var request = context.ActionContext.HttpContext.Request; + if (request.HasFormContentType) + { + // Allocating a Task only when the body is form data. + return AddValueProviderAsync(context); + } + + return Task.CompletedTask; + } + + private static async Task AddValueProviderAsync(ValueProviderFactoryContext context) + { + var request = context.ActionContext.HttpContext.Request; + + var formCollection = await request.ReadFormAsync(); + + var valueProvider = new JQueryFormValueProvider( + BindingSource.Form, + JQueryKeyValuePairNormalizer.GetValues(formCollection, formCollection.Count), + CultureInfo.CurrentCulture); + + context.ValueProviders.Add(valueProvider); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryKeyValuePairNormalizer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryKeyValuePairNormalizer.cs new file mode 100644 index 0000000000..b0c1c28c0f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryKeyValuePairNormalizer.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.Text; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + // Normalizes keys, in a keyvaluepair collection, from jQuery format to a format that MVC understands. + internal static class JQueryKeyValuePairNormalizer + { + public static IDictionary GetValues( + IEnumerable> originalValues, + int valueCount) + { + var builder = new StringBuilder(); + var dictionary = new Dictionary( + valueCount, + StringComparer.OrdinalIgnoreCase); + foreach (var originalValue in originalValues) + { + var normalizedKey = NormalizeJQueryToMvc(builder, originalValue.Key); + builder.Clear(); + + dictionary[normalizedKey] = originalValue.Value; + } + + return dictionary; + } + + // This is a helper method for Model Binding over a JQuery syntax. + // Normalize from JQuery to MVC keys. The model binding infrastructure uses MVC keys. + // x[] --> x + // [] --> "" + // x[12] --> x[12] + // x[field] --> x.field, where field is not a number + private static string NormalizeJQueryToMvc(StringBuilder builder, string key) + { + if (string.IsNullOrEmpty(key)) + { + return string.Empty; + } + + var indexOpen = key.IndexOf('['); + if (indexOpen == -1) + { + + // Fast path, no normalization needed. + // This skips string conversion and allocating the string builder. + return key; + } + + var position = 0; + while (position < key.Length) + { + if (indexOpen == -1) + { + // No more brackets. + builder.Append(key, position, key.Length - position); + break; + } + + builder.Append(key, position, indexOpen - position); // everything up to "[" + + // Find closing bracket. + var indexClose = key.IndexOf(']', indexOpen); + if (indexClose == -1) + { + throw new ArgumentException( + message: Resources.FormatJQueryFormValueProviderFactory_MissingClosingBracket(key), + paramName: nameof(key)); + } + + if (indexClose == indexOpen + 1) + { + // Empty brackets signify an array. Just remove. + } + else if (char.IsDigit(key[indexOpen + 1])) + { + // Array index. Leave unchanged. + builder.Append(key, indexOpen, indexClose - indexOpen + 1); + } + else + { + // Field name. Convert to dot notation. + if (builder.Length != 0) + { + // Was x[field], not [field] or [][field]. + builder.Append('.'); + } + + builder.Append(key, indexOpen + 1, indexClose - indexOpen - 1); + } + + position = indexClose + 1; + indexOpen = key.IndexOf('[', position); + } + + return builder.ToString(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProvider.cs new file mode 100644 index 0000000000..4d50ab8b88 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProvider.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.Collections.Generic; +using System.Globalization; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An for jQuery formatted query string data. + /// + public class JQueryQueryStringValueProvider : JQueryValueProvider + { + /// + /// Initializes a new instance of the class. + /// + /// The of the data. + /// The values. + /// The culture to return with ValueProviderResult instances. + public JQueryQueryStringValueProvider( + BindingSource bindingSource, + IDictionary values, + CultureInfo culture) + : base(bindingSource, values, culture) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProviderFactory.cs new file mode 100644 index 0000000000..f454bb6216 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProviderFactory.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; +using System.Globalization; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An for . + /// + public class JQueryQueryStringValueProviderFactory : IValueProviderFactory + { + /// + public Task CreateValueProviderAsync(ValueProviderFactoryContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var query = context.ActionContext.HttpContext.Request.Query; + if (query != null && query.Count > 0) + { + var valueProvider = new JQueryQueryStringValueProvider( + BindingSource.Query, + JQueryKeyValuePairNormalizer.GetValues(query, query.Count), + CultureInfo.InvariantCulture); + + context.ValueProviders.Add(valueProvider); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryValueProvider.cs new file mode 100644 index 0000000000..659a943934 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryValueProvider.cs @@ -0,0 +1,103 @@ +// Copyright (c) .NET 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.Mvc.Internal; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An for jQuery formatted data. + /// + public abstract class JQueryValueProvider : + BindingSourceValueProvider, + IEnumerableValueProvider, + IKeyRewriterValueProvider + { + private readonly IDictionary _values; + private PrefixContainer _prefixContainer; + + /// + /// Initializes a new instance of the class. + /// + /// The of the data. + /// The values. + /// The culture to return with ValueProviderResult instances. + protected JQueryValueProvider( + BindingSource bindingSource, + IDictionary values, + CultureInfo culture) + : base(bindingSource) + { + if (bindingSource == null) + { + throw new ArgumentNullException(nameof(bindingSource)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + _values = values; + Culture = culture; + } + + /// + /// Gets the associated with the values. + /// + public CultureInfo Culture { get; } + + /// + protected PrefixContainer PrefixContainer + { + get + { + if (_prefixContainer == null) + { + _prefixContainer = new PrefixContainer(_values.Keys); + } + + return _prefixContainer; + } + } + + /// + public override bool ContainsPrefix(string prefix) + { + return PrefixContainer.ContainsPrefix(prefix); + } + + /// + public IDictionary GetKeysFromPrefix(string prefix) + { + return PrefixContainer.GetKeysFromPrefix(prefix); + } + + /// + public override ValueProviderResult GetValue(string key) + { + StringValues values; + if (_values.TryGetValue(key, out values) && values.Count > 0) + { + return new ValueProviderResult(values, Culture); + } + + return ValueProviderResult.None; + } + + /// + /// + /// Always returns because creates this + /// with rewritten keys (if original contains brackets) or duplicate keys + /// (that will match). + /// + public IValueProvider Filter() + { + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs new file mode 100644 index 0000000000..3714298b69 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET 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.ModelBinding.Metadata +{ + /// + /// Binding metadata details for a . + /// + public class BindingMetadata + { + private DefaultModelBindingMessageProvider _messageProvider; + + /// + /// Gets or sets the . + /// See . + /// + public BindingSource BindingSource { get; set; } + + /// + /// Gets or sets the binder model name. If null the property or parameter name will be used. + /// See . + /// + public string BinderModelName { get; set; } + + /// + /// Gets or sets the of the model binder used to bind the model. + /// See . + /// + public Type BinderType { get; set; } + + /// + /// Gets or sets a value indicating whether or not the property can be model bound. + /// Will be ignored if the model metadata being created does not represent a property. + /// See . + /// + public bool IsBindingAllowed { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not the request must contain a value for the model. + /// Will be ignored if the model metadata being created does not represent a property. + /// See . + /// + public bool IsBindingRequired { get; set; } + + /// + /// Gets or sets a value indicating whether or not the model is read-only. Will be ignored + /// if the model metadata being created is not a property. If null then + /// will be computed based on the accessibility + /// of the property accessor and model . See . + /// + public bool? IsReadOnly { get; set; } + + /// + /// Gets the instance. See + /// . + /// + public DefaultModelBindingMessageProvider ModelBindingMessageProvider + { + get => _messageProvider; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _messageProvider = value; + } + } + + /// + /// Gets or sets the . + /// See . + /// + public IPropertyFilterProvider PropertyFilterProvider { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadataProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadataProviderContext.cs new file mode 100644 index 0000000000..587cf6fb69 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadataProviderContext.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// A context for an . + /// + public class BindingMetadataProviderContext + { + /// + /// Creates a new . + /// + /// The for the . + /// The attributes for the . + public BindingMetadataProviderContext( + ModelMetadataIdentity key, + ModelAttributes attributes) + { + if (attributes == null) + { + throw new ArgumentNullException(nameof(attributes)); + } + + Key = key; + Attributes = attributes.Attributes; + ParameterAttributes = attributes.ParameterAttributes; + PropertyAttributes = attributes.PropertyAttributes; + TypeAttributes = attributes.TypeAttributes; + + BindingMetadata = new BindingMetadata(); + } + + /// + /// Gets the attributes. + /// + public IReadOnlyList Attributes { get; } + + /// + /// Gets the . + /// + public ModelMetadataIdentity Key { get; } + + /// + /// Gets the parameter attributes. + /// + public IReadOnlyList ParameterAttributes { get; } + + /// + /// Gets the property attributes. + /// + public IReadOnlyList PropertyAttributes { get; } + + /// + /// Gets the type attributes. + /// + public IReadOnlyList TypeAttributes { get; } + + /// + /// Gets the . + /// + public BindingMetadata BindingMetadata { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingSourceMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingSourceMetadataProvider.cs new file mode 100644 index 0000000000..20c88b013f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingSourceMetadataProvider.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + public class BindingSourceMetadataProvider : IBindingMetadataProvider + { + /// + /// Creates a new for the given . + /// + /// + /// The . The provider sets of the given or + /// anything assignable to the given . + /// + /// + /// The to assign to the given . + /// + public BindingSourceMetadataProvider(Type type, BindingSource bindingSource) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + Type = type; + BindingSource = bindingSource; + } + + public Type Type { get; } + public BindingSource BindingSource { get; } + + /// + public void CreateBindingMetadata(BindingMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (Type.IsAssignableFrom(context.Key.ModelType)) + { + context.BindingMetadata.BindingSource = BindingSource; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultMetadataDetails.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultMetadataDetails.cs new file mode 100644 index 0000000000..cc9b540c5a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultMetadataDetails.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 System; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// Holds associated metadata objects for a . + /// + /// + /// Any modifications to the data must be thread-safe for multiple readers and writers. + /// + public class DefaultMetadataDetails + { + /// + /// Creates a new . + /// + /// The . + /// The set of model attributes. + public DefaultMetadataDetails(ModelMetadataIdentity key, ModelAttributes attributes) + { + if (attributes == null) + { + throw new ArgumentNullException(nameof(attributes)); + } + + Key = key; + ModelAttributes = attributes; + } + + /// + /// Gets or sets the set of model attributes. + /// + public ModelAttributes ModelAttributes { get; } + + /// + /// Gets or sets the . + /// + public BindingMetadata BindingMetadata { get; set; } + + /// + /// Gets or sets the . + /// + public DisplayMetadata DisplayMetadata { get; set; } + + /// + /// Gets or sets the . + /// + public ModelMetadataIdentity Key { get; } + + /// + /// Gets or sets the entries for the model properties. + /// + public ModelMetadata[] Properties { get; set; } + + /// + /// Gets or sets a property getter delegate to get the property value from a model object. + /// + public Func PropertyGetter { get; set; } + + /// + /// Gets or sets a property setter delegate to set the property value on a model object. + /// + public Action PropertySetter { get; set; } + + /// + /// Gets or sets the + /// + public ValidationMetadata ValidationMetadata { get; set; } + + /// + /// Gets or sets the of the container type. + /// + public ModelMetadata ContainerMetadata { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelBindingMessageProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelBindingMessageProvider.cs new file mode 100644 index 0000000000..30cffb2147 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelBindingMessageProvider.cs @@ -0,0 +1,257 @@ +// Copyright (c) .NET 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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// Read / write implementation. + /// + public class DefaultModelBindingMessageProvider : ModelBindingMessageProvider + { + private Func _missingBindRequiredValueAccessor; + private Func _missingKeyOrValueAccessor; + private Func _missingRequestBodyRequiredValueAccessor; + private Func _valueMustNotBeNullAccessor; + private Func _attemptedValueIsInvalidAccessor; + private Func _nonPropertyAttemptedValueIsInvalidAccessor; + private Func _unknownValueIsInvalidAccessor; + private Func _nonPropertyUnknownValueIsInvalidAccessor; + private Func _valueIsInvalidAccessor; + private Func _valueMustBeANumberAccessor; + private Func _nonPropertyValueMustBeANumberAccessor; + + /// + /// Initializes a new instance of the class. + /// + public DefaultModelBindingMessageProvider() + { + SetMissingBindRequiredValueAccessor(Resources.FormatModelBinding_MissingBindRequiredMember); + SetMissingKeyOrValueAccessor(Resources.FormatKeyValuePair_BothKeyAndValueMustBePresent); + SetMissingRequestBodyRequiredValueAccessor(Resources.FormatModelBinding_MissingRequestBodyRequiredMember); + SetValueMustNotBeNullAccessor(Resources.FormatModelBinding_NullValueNotValid); + SetAttemptedValueIsInvalidAccessor(Resources.FormatModelState_AttemptedValueIsInvalid); + SetNonPropertyAttemptedValueIsInvalidAccessor(Resources.FormatModelState_NonPropertyAttemptedValueIsInvalid); + SetUnknownValueIsInvalidAccessor(Resources.FormatModelState_UnknownValueIsInvalid); + SetNonPropertyUnknownValueIsInvalidAccessor(Resources.FormatModelState_NonPropertyUnknownValueIsInvalid); + SetValueIsInvalidAccessor(Resources.FormatHtmlGeneration_ValueIsInvalid); + SetValueMustBeANumberAccessor(Resources.FormatHtmlGeneration_ValueMustBeNumber); + SetNonPropertyValueMustBeANumberAccessor(Resources.FormatHtmlGeneration_NonPropertyValueMustBeNumber); + } + + /// + /// Initializes a new instance of the class based on + /// . + /// + /// The to duplicate. + public DefaultModelBindingMessageProvider(DefaultModelBindingMessageProvider originalProvider) + { + if (originalProvider == null) + { + throw new ArgumentNullException(nameof(originalProvider)); + } + + SetMissingBindRequiredValueAccessor(originalProvider.MissingBindRequiredValueAccessor); + SetMissingKeyOrValueAccessor(originalProvider.MissingKeyOrValueAccessor); + SetMissingRequestBodyRequiredValueAccessor(originalProvider.MissingRequestBodyRequiredValueAccessor); + SetValueMustNotBeNullAccessor(originalProvider.ValueMustNotBeNullAccessor); + SetAttemptedValueIsInvalidAccessor(originalProvider.AttemptedValueIsInvalidAccessor); + SetNonPropertyAttemptedValueIsInvalidAccessor(originalProvider.NonPropertyAttemptedValueIsInvalidAccessor); + SetUnknownValueIsInvalidAccessor(originalProvider.UnknownValueIsInvalidAccessor); + SetNonPropertyUnknownValueIsInvalidAccessor(originalProvider.NonPropertyUnknownValueIsInvalidAccessor); + SetValueIsInvalidAccessor(originalProvider.ValueIsInvalidAccessor); + SetValueMustBeANumberAccessor(originalProvider.ValueMustBeANumberAccessor); + SetNonPropertyValueMustBeANumberAccessor(originalProvider.NonPropertyValueMustBeANumberAccessor); + } + + /// + public override Func MissingBindRequiredValueAccessor => _missingBindRequiredValueAccessor; + + /// + /// Sets the property. + /// + /// The value to set. + public void SetMissingBindRequiredValueAccessor(Func missingBindRequiredValueAccessor) + { + if (missingBindRequiredValueAccessor == null) + { + throw new ArgumentNullException(nameof(missingBindRequiredValueAccessor)); + } + + _missingBindRequiredValueAccessor = missingBindRequiredValueAccessor; + } + + /// + public override Func MissingKeyOrValueAccessor => _missingKeyOrValueAccessor; + + /// + /// Sets the property. + /// + /// The value to set. + public void SetMissingKeyOrValueAccessor(Func missingKeyOrValueAccessor) + { + if (missingKeyOrValueAccessor == null) + { + throw new ArgumentNullException(nameof(missingKeyOrValueAccessor)); + } + + _missingKeyOrValueAccessor = missingKeyOrValueAccessor; + } + + /// + public override Func MissingRequestBodyRequiredValueAccessor => _missingRequestBodyRequiredValueAccessor; + + /// + /// Sets the property. + /// + /// The value to set. + public void SetMissingRequestBodyRequiredValueAccessor(Func missingRequestBodyRequiredValueAccessor) + { + if (missingRequestBodyRequiredValueAccessor == null) + { + throw new ArgumentNullException(nameof(missingRequestBodyRequiredValueAccessor)); + } + + _missingRequestBodyRequiredValueAccessor = missingRequestBodyRequiredValueAccessor; + } + + /// + public override Func ValueMustNotBeNullAccessor => _valueMustNotBeNullAccessor; + + /// + /// Sets the property. + /// + /// The value to set. + public void SetValueMustNotBeNullAccessor(Func valueMustNotBeNullAccessor) + { + if (valueMustNotBeNullAccessor == null) + { + throw new ArgumentNullException(nameof(valueMustNotBeNullAccessor)); + } + + _valueMustNotBeNullAccessor = valueMustNotBeNullAccessor; + } + + /// + public override Func AttemptedValueIsInvalidAccessor => _attemptedValueIsInvalidAccessor; + + /// + /// Sets the property. + /// + /// The value to set. + public void SetAttemptedValueIsInvalidAccessor(Func attemptedValueIsInvalidAccessor) + { + if (attemptedValueIsInvalidAccessor == null) + { + throw new ArgumentNullException(nameof(attemptedValueIsInvalidAccessor)); + } + + _attemptedValueIsInvalidAccessor = attemptedValueIsInvalidAccessor; + } + + /// + public override Func NonPropertyAttemptedValueIsInvalidAccessor => _nonPropertyAttemptedValueIsInvalidAccessor; + + /// + /// Sets the property. + /// + /// The value to set. + public void SetNonPropertyAttemptedValueIsInvalidAccessor( + Func nonPropertyAttemptedValueIsInvalidAccessor) + { + if (nonPropertyAttemptedValueIsInvalidAccessor == null) + { + throw new ArgumentNullException(nameof(nonPropertyAttemptedValueIsInvalidAccessor)); + } + + _nonPropertyAttemptedValueIsInvalidAccessor = nonPropertyAttemptedValueIsInvalidAccessor; + } + + /// + public override Func UnknownValueIsInvalidAccessor => _unknownValueIsInvalidAccessor; + + /// + /// Sets the property. + /// + /// The value to set. + public void SetUnknownValueIsInvalidAccessor(Func unknownValueIsInvalidAccessor) + { + if (unknownValueIsInvalidAccessor == null) + { + throw new ArgumentNullException(nameof(unknownValueIsInvalidAccessor)); + } + + _unknownValueIsInvalidAccessor = unknownValueIsInvalidAccessor; + } + + /// + public override Func NonPropertyUnknownValueIsInvalidAccessor => _nonPropertyUnknownValueIsInvalidAccessor; + + /// + /// Sets the property. + /// + /// The value to set. + public void SetNonPropertyUnknownValueIsInvalidAccessor(Func nonPropertyUnknownValueIsInvalidAccessor) + { + if (nonPropertyUnknownValueIsInvalidAccessor == null) + { + throw new ArgumentNullException(nameof(nonPropertyUnknownValueIsInvalidAccessor)); + } + + _nonPropertyUnknownValueIsInvalidAccessor = nonPropertyUnknownValueIsInvalidAccessor; + } + + /// + public override Func ValueIsInvalidAccessor => _valueIsInvalidAccessor; + + /// + /// Sets the property. + /// + /// The value to set. + public void SetValueIsInvalidAccessor(Func valueIsInvalidAccessor) + { + if (valueIsInvalidAccessor == null) + { + throw new ArgumentNullException(nameof(valueIsInvalidAccessor)); + } + + _valueIsInvalidAccessor = valueIsInvalidAccessor; + } + + /// + public override Func ValueMustBeANumberAccessor => _valueMustBeANumberAccessor; + + /// + /// Sets the property. + /// + /// The value to set. + public void SetValueMustBeANumberAccessor(Func valueMustBeANumberAccessor) + { + if (valueMustBeANumberAccessor == null) + { + throw new ArgumentNullException(nameof(valueMustBeANumberAccessor)); + } + + _valueMustBeANumberAccessor = valueMustBeANumberAccessor; + } + + /// + public override Func NonPropertyValueMustBeANumberAccessor => _nonPropertyValueMustBeANumberAccessor; + + /// + /// Sets the property. + /// + /// The value to set. + public void SetNonPropertyValueMustBeANumberAccessor(Func nonPropertyValueMustBeANumberAccessor) + { + if (nonPropertyValueMustBeANumberAccessor == null) + { + throw new ArgumentNullException(nameof(nonPropertyValueMustBeANumberAccessor)); + } + + _nonPropertyValueMustBeANumberAccessor = nonPropertyValueMustBeANumberAccessor; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs new file mode 100644 index 0000000000..bfb1bde8b5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs @@ -0,0 +1,462 @@ +// Copyright (c) .NET 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.Linq; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// A default implementation. + /// + public class DefaultModelMetadata : ModelMetadata + { + private readonly IModelMetadataProvider _provider; + private readonly ICompositeMetadataDetailsProvider _detailsProvider; + private readonly DefaultMetadataDetails _details; + + // Default message provider for all DefaultModelMetadata instances; cloned before exposing to + // IBindingMetadataProvider instances to ensure customizations are not accidentally shared. + private readonly DefaultModelBindingMessageProvider _modelBindingMessageProvider; + + private ReadOnlyDictionary _additionalValues; + private ModelMetadata _elementMetadata; + private bool? _isBindingRequired; + private bool? _isReadOnly; + private bool? _isRequired; + private ModelPropertyCollection _properties; + private bool? _validateChildren; + private ReadOnlyCollection _validatorMetadata; + + /// + /// Creates a new . + /// + /// The . + /// The . + /// The . + public DefaultModelMetadata( + IModelMetadataProvider provider, + ICompositeMetadataDetailsProvider detailsProvider, + DefaultMetadataDetails details) + : this(provider, detailsProvider, details, new DefaultModelBindingMessageProvider()) + { + } + + /// + /// Creates a new . + /// + /// The . + /// The . + /// The . + /// The . + public DefaultModelMetadata( + IModelMetadataProvider provider, + ICompositeMetadataDetailsProvider detailsProvider, + DefaultMetadataDetails details, + DefaultModelBindingMessageProvider modelBindingMessageProvider) + : base(details.Key) + { + if (provider == null) + { + throw new ArgumentNullException(nameof(provider)); + } + + if (detailsProvider == null) + { + throw new ArgumentNullException(nameof(detailsProvider)); + } + + if (details == null) + { + throw new ArgumentNullException(nameof(details)); + } + + if (modelBindingMessageProvider == null) + { + throw new ArgumentNullException(nameof(modelBindingMessageProvider)); + } + + _provider = provider; + _detailsProvider = detailsProvider; + _details = details; + _modelBindingMessageProvider = modelBindingMessageProvider; + } + + /// + /// Gets the set of attributes for the current instance. + /// + public ModelAttributes Attributes => _details.ModelAttributes; + + /// + public override ModelMetadata ContainerMetadata => _details.ContainerMetadata; + + /// + /// Gets the for the current instance. + /// + /// + /// Accessing this property will populate the if necessary. + /// + public BindingMetadata BindingMetadata + { + get + { + if (_details.BindingMetadata == null) + { + var context = new BindingMetadataProviderContext(Identity, _details.ModelAttributes); + + // Provide a unique ModelBindingMessageProvider instance so providers' customizations are per-type. + context.BindingMetadata.ModelBindingMessageProvider = + new DefaultModelBindingMessageProvider(_modelBindingMessageProvider); + + _detailsProvider.CreateBindingMetadata(context); + _details.BindingMetadata = context.BindingMetadata; + } + + return _details.BindingMetadata; + } + } + + /// + /// Gets the for the current instance. + /// + /// + /// Accessing this property will populate the if necessary. + /// + public DisplayMetadata DisplayMetadata + { + get + { + if (_details.DisplayMetadata == null) + { + var context = new DisplayMetadataProviderContext(Identity, _details.ModelAttributes); + _detailsProvider.CreateDisplayMetadata(context); + _details.DisplayMetadata = context.DisplayMetadata; + } + + return _details.DisplayMetadata; + } + } + + /// + /// Gets the for the current instance. + /// + /// + /// Accessing this property will populate the if necessary. + /// + public ValidationMetadata ValidationMetadata + { + get + { + if (_details.ValidationMetadata == null) + { + var context = new ValidationMetadataProviderContext(Identity, _details.ModelAttributes); + _detailsProvider.CreateValidationMetadata(context); + _details.ValidationMetadata = context.ValidationMetadata; + } + + return _details.ValidationMetadata; + } + } + + /// + public override IReadOnlyDictionary AdditionalValues + { + get + { + if (_additionalValues == null) + { + _additionalValues = new ReadOnlyDictionary(DisplayMetadata.AdditionalValues); + } + + return _additionalValues; + } + } + + /// + public override BindingSource BindingSource => BindingMetadata.BindingSource; + + /// + public override string BinderModelName => BindingMetadata.BinderModelName; + + /// + public override Type BinderType => BindingMetadata.BinderType; + + /// + public override bool ConvertEmptyStringToNull => DisplayMetadata.ConvertEmptyStringToNull; + + /// + public override string DataTypeName => DisplayMetadata.DataTypeName; + + /// + public override string Description + { + get + { + if (DisplayMetadata.Description == null) + { + return null; + } + + return DisplayMetadata.Description(); + } + } + + /// + public override string DisplayFormatString => DisplayMetadata.DisplayFormatStringProvider(); + + /// + public override string DisplayName + { + get + { + if (DisplayMetadata.DisplayName == null) + { + return null; + } + + return DisplayMetadata.DisplayName(); + } + } + + /// + public override string EditFormatString => DisplayMetadata.EditFormatStringProvider(); + + /// + public override ModelMetadata ElementMetadata + { + get + { + if (_elementMetadata == null && ElementType != null) + { + _elementMetadata = _provider.GetMetadataForType(ElementType); + } + + return _elementMetadata; + } + } + + /// + public override IEnumerable> EnumGroupedDisplayNamesAndValues + => DisplayMetadata.EnumGroupedDisplayNamesAndValues; + + /// + public override IReadOnlyDictionary EnumNamesAndValues => DisplayMetadata.EnumNamesAndValues; + + /// + public override bool HasNonDefaultEditFormat => DisplayMetadata.HasNonDefaultEditFormat; + + /// + public override bool HideSurroundingHtml => DisplayMetadata.HideSurroundingHtml; + + /// + public override bool HtmlEncode => DisplayMetadata.HtmlEncode; + + /// + public override bool IsBindingAllowed + { + get + { + if (MetadataKind == ModelMetadataKind.Type) + { + return true; + } + else + { + return BindingMetadata.IsBindingAllowed; + } + } + } + + /// + public override bool IsBindingRequired + { + get + { + if (!_isBindingRequired.HasValue) + { + if (MetadataKind == ModelMetadataKind.Type) + { + _isBindingRequired = false; + } + else + { + _isBindingRequired = BindingMetadata.IsBindingRequired; + } + } + + return _isBindingRequired.Value; + } + } + + /// + public override bool IsEnum => DisplayMetadata.IsEnum; + + /// + public override bool IsFlagsEnum => DisplayMetadata.IsFlagsEnum; + + /// + public override bool IsReadOnly + { + get + { + if (!_isReadOnly.HasValue) + { + if (MetadataKind == ModelMetadataKind.Type) + { + _isReadOnly = false; + } + else if (BindingMetadata.IsReadOnly.HasValue) + { + _isReadOnly = BindingMetadata.IsReadOnly; + } + else + { + _isReadOnly = _details.PropertySetter == null; + } + } + + return _isReadOnly.Value; + } + } + + /// + public override bool IsRequired + { + get + { + if (!_isRequired.HasValue) + { + if (ValidationMetadata.IsRequired.HasValue) + { + _isRequired = ValidationMetadata.IsRequired; + } + else + { + // Default to IsRequired = true for non-Nullable value types. + _isRequired = !IsReferenceOrNullableType; + } + } + + return _isRequired.Value; + } + } + + /// + public override ModelBindingMessageProvider ModelBindingMessageProvider => + BindingMetadata.ModelBindingMessageProvider; + + /// + public override string NullDisplayText => DisplayMetadata.NullDisplayTextProvider(); + + /// + public override int Order => DisplayMetadata.Order; + + /// + public override string Placeholder + { + get + { + if (DisplayMetadata.Placeholder == null) + { + return null; + } + + return DisplayMetadata.Placeholder(); + } + } + + /// + public override ModelPropertyCollection Properties + { + get + { + if (_properties == null) + { + var properties = _provider.GetMetadataForProperties(ModelType); + properties = properties.OrderBy(p => p.Order); + _properties = new ModelPropertyCollection(properties); + } + + return _properties; + } + } + + /// + public override IPropertyFilterProvider PropertyFilterProvider => BindingMetadata.PropertyFilterProvider; + + /// + public override bool ShowForDisplay => DisplayMetadata.ShowForDisplay; + + /// + public override bool ShowForEdit => DisplayMetadata.ShowForEdit; + + /// + public override string SimpleDisplayProperty => DisplayMetadata.SimpleDisplayProperty; + + /// + public override string TemplateHint => DisplayMetadata.TemplateHint; + + /// + public override IPropertyValidationFilter PropertyValidationFilter => ValidationMetadata.PropertyValidationFilter; + + /// + public override bool ValidateChildren + { + get + { + if (!_validateChildren.HasValue) + { + if (ValidationMetadata.ValidateChildren.HasValue) + { + _validateChildren = ValidationMetadata.ValidateChildren.Value; + } + else if (IsComplexType || IsEnumerableType) + { + _validateChildren = true; + } + else + { + _validateChildren = false; + } + } + + return _validateChildren.Value; + } + } + + /// + public override IReadOnlyList ValidatorMetadata + { + get + { + if (_validatorMetadata == null) + { + _validatorMetadata = new ReadOnlyCollection(ValidationMetadata.ValidatorMetadata); + } + + return _validatorMetadata; + } + } + + /// + public override Func PropertyGetter => _details.PropertyGetter; + + /// + public override Action PropertySetter => _details.PropertySetter; + + /// + public override ModelMetadata GetMetadataForType(Type modelType) + { + return _provider.GetMetadataForType(modelType); + } + + /// + public override IEnumerable GetMetadataForProperties(Type modelType) + { + return _provider.GetMetadataForProperties(modelType); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs new file mode 100644 index 0000000000..e15c0c3532 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs @@ -0,0 +1,383 @@ +// Copyright (c) .NET 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.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// A default implementation of based on reflection. + /// + public class DefaultModelMetadataProvider : ModelMetadataProvider, IModelMetadataProvider2 + { + private static readonly Func _modelMetadataIdentityForParameter; + + private readonly TypeCache _typeCache = new TypeCache(); + private readonly Func _cacheEntryFactory; + private readonly ModelMetadataCacheEntry _metadataCacheEntryForObjectType; + + static DefaultModelMetadataProvider() + { + var forParameterMethod = typeof(ModelMetadataIdentity).GetMethod( + nameof(ModelMetadataIdentity.ForParameter), + BindingFlags.Static | BindingFlags.NonPublic, + binder: null, + types: new[] { typeof(ParameterInfo), typeof(Type) }, + modifiers: null); + + _modelMetadataIdentityForParameter = (Func) + forParameterMethod.CreateDelegate(typeof(Func)); + } + + /// + /// Creates a new . + /// + /// The . + public DefaultModelMetadataProvider(ICompositeMetadataDetailsProvider detailsProvider) + : this(detailsProvider, new DefaultModelBindingMessageProvider()) + { + } + + /// + /// Creates a new . + /// + /// The . + /// The accessor for . + public DefaultModelMetadataProvider( + ICompositeMetadataDetailsProvider detailsProvider, + IOptions optionsAccessor) + : this(detailsProvider, GetMessageProvider(optionsAccessor)) + { + } + + private DefaultModelMetadataProvider( + ICompositeMetadataDetailsProvider detailsProvider, + DefaultModelBindingMessageProvider modelBindingMessageProvider) + { + if (detailsProvider == null) + { + throw new ArgumentNullException(nameof(detailsProvider)); + } + + DetailsProvider = detailsProvider; + ModelBindingMessageProvider = modelBindingMessageProvider; + + _cacheEntryFactory = CreateCacheEntry; + _metadataCacheEntryForObjectType = GetMetadataCacheEntryForObjectType(); + } + + /// + /// Gets the . + /// + protected ICompositeMetadataDetailsProvider DetailsProvider { get; } + + /// + /// Gets the . + /// + /// Same as in all production scenarios. + protected DefaultModelBindingMessageProvider ModelBindingMessageProvider { get; } + + /// + public override IEnumerable GetMetadataForProperties(Type modelType) + { + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + 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. + if (cacheEntry.Details.Properties == null) + { + var key = ModelMetadataIdentity.ForType(modelType); + var propertyDetails = CreatePropertyDetails(key); + + var properties = new ModelMetadata[propertyDetails.Length]; + for (var i = 0; i < properties.Length; i++) + { + propertyDetails[i].ContainerMetadata = cacheEntry.Metadata; + properties[i] = CreateModelMetadata(propertyDetails[i]); + } + + cacheEntry.Details.Properties = properties; + } + + return cacheEntry.Details.Properties; + } + + /// + public override ModelMetadata GetMetadataForParameter(ParameterInfo parameter) + => GetMetadataForParameter(parameter, parameter?.ParameterType); + + /// + ModelMetadata IModelMetadataProvider2.GetMetadataForParameter(ParameterInfo parameter, Type modelType) + => GetMetadataForParameter(parameter, modelType); + + internal ModelMetadata GetMetadataForParameter(ParameterInfo parameter, Type modelType) + { + if (parameter == null) + { + throw new ArgumentNullException(nameof(parameter)); + } + + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + var cacheEntry = GetCacheEntry(parameter, modelType); + + return cacheEntry.Metadata; + } + + /// + public override ModelMetadata GetMetadataForType(Type modelType) + { + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + var cacheEntry = GetCacheEntry(modelType); + + return cacheEntry.Metadata; + } + + + /// + ModelMetadata IModelMetadataProvider2.GetMetadataForProperty(PropertyInfo parameter, Type modelType) + => GetMetadataForProperty(parameter, modelType); + + internal 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) + { + throw new ArgumentNullException(nameof(optionsAccessor)); + } + + return optionsAccessor.Value.ModelBindingMessageProvider; + } + + private ModelMetadataCacheEntry GetCacheEntry(Type modelType) + { + ModelMetadataCacheEntry cacheEntry; + + // Perf: We cached model metadata cache entry for "object" type to save ConcurrentDictionary lookups. + if (modelType == typeof(object)) + { + cacheEntry = _metadataCacheEntryForObjectType; + } + else + { + var key = ModelMetadataIdentity.ForType(modelType); + + cacheEntry = _typeCache.GetOrAdd(key, _cacheEntryFactory); + } + + return cacheEntry; + } + + private ModelMetadataCacheEntry GetCacheEntry(ParameterInfo parameter, Type modelType) + { + return _typeCache.GetOrAdd( + _modelMetadataIdentityForParameter(parameter, modelType), + _cacheEntryFactory); + } + + private ModelMetadataCacheEntry GetCacheEntry(PropertyInfo property, Type modelType) + { + return _typeCache.GetOrAdd( + ModelMetadataIdentity.ForProperty(modelType, property.Name, property.DeclaringType), + _cacheEntryFactory); + } + + private ModelMetadataCacheEntry CreateCacheEntry(ModelMetadataIdentity key) + { + DefaultMetadataDetails details; + if (key.MetadataKind == ModelMetadataKind.Parameter) + { + details = CreateParameterDetails(key); + } + else if (key.MetadataKind == ModelMetadataKind.Property) + { + details = CreateSinglePropertyDetails(key); + } + else + { + details = CreateTypeDetails(key); + } + + var metadata = CreateModelMetadata(details); + 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)); + var entry = CreateCacheEntry(key); + return entry; + } + + /// + /// Creates a new from a . + /// + /// The entry with cached data. + /// A new instance. + /// + /// will always create instances of + /// .Override this method to create a + /// of a different concrete type. + /// + protected virtual ModelMetadata CreateModelMetadata(DefaultMetadataDetails entry) + { + return new DefaultModelMetadata(this, DetailsProvider, entry, ModelBindingMessageProvider); + } + + /// + /// Creates the entries for the properties of a model + /// . + /// + /// + /// The identifying the model . + /// + /// A details object for each property of the model . + /// + /// The results of this method will be cached and used to satisfy calls to + /// . Override this method to provide a different + /// set of property data. + /// + protected virtual DefaultMetadataDetails[] CreatePropertyDetails(ModelMetadataIdentity key) + { + var propertyHelpers = PropertyHelper.GetVisibleProperties(key.ModelType); + + var propertyEntries = new List(propertyHelpers.Length); + for (var i = 0; i < propertyHelpers.Length; i++) + { + var propertyHelper = propertyHelpers[i]; + + var propertyKey = ModelMetadataIdentity.ForProperty( + propertyHelper.Property.PropertyType, + propertyHelper.Name, + key.ModelType); + + 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 . + /// + /// + /// The identifying the model . + /// + /// A details object for the model . + /// + /// The results of this method will be cached and used to satisfy calls to + /// . Override this method to provide a different + /// set of attributes. + /// + protected virtual DefaultMetadataDetails CreateTypeDetails(ModelMetadataIdentity key) + { + return new DefaultMetadataDetails( + key, + ModelAttributes.GetAttributesForType(key.ModelType)); + } + + protected virtual DefaultMetadataDetails CreateParameterDetails(ModelMetadataIdentity key) + { + return new DefaultMetadataDetails( + key, + ModelAttributes.GetAttributesForParameter(key.ParameterInfo, key.ModelType)); + } + + private class TypeCache : ConcurrentDictionary + { + } + + private struct ModelMetadataCacheEntry + { + public ModelMetadataCacheEntry(ModelMetadata metadata, DefaultMetadataDetails details) + { + Metadata = metadata; + Details = details; + } + + public ModelMetadata Metadata { get; } + + public DefaultMetadataDetails Details { get; } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs new file mode 100644 index 0000000000..7d2f15ce36 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs @@ -0,0 +1,273 @@ +// Copyright (c) .NET 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.ModelBinding.Metadata +{ + /// + /// Display metadata details for a . + /// + public class DisplayMetadata + { + private Func _displayFormatStringProvider = () => null; + private Func _editFormatStringProvider = () => null; + private Func _nullDisplayTextProvider = () => null; + + /// + /// Gets a set of additional values. See + /// + public IDictionary AdditionalValues { get; } = new Dictionary(); + + /// + /// Gets or sets a value indicating whether or not to convert an empty string value or one containing only + /// whitespace characters to when representing a model as text. See + /// + /// + public bool ConvertEmptyStringToNull { get; set; } = true; + + /// + /// Gets or sets the name of the data type. + /// See + /// + public string DataTypeName { get; set; } + + /// + /// Gets or sets a delegate which is used to get a value for the + /// model description. See . + /// + public Func Description { get; set; } + + /// + /// Gets or sets a display format string for the model. + /// See + /// + /// + /// Setting also changes . + /// + public string DisplayFormatString + { + get + { + return DisplayFormatStringProvider(); + } + set + { + DisplayFormatStringProvider = () => value; + } + } + + /// + /// Gets or sets a delegate which is used to get the display format string for the model. See + /// . + /// + /// + /// Setting also changes . + /// + public Func DisplayFormatStringProvider + { + get + { + return _displayFormatStringProvider; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _displayFormatStringProvider = value; + } + } + + /// + /// Gets or sets a delegate which is used to get a value for the + /// display name of the model. See . + /// + public Func DisplayName { get; set; } + + /// + /// Gets or sets an edit format string for the model. + /// See + /// + /// + /// + /// Setting also changes . + /// + /// + /// instances that set this property to a non-, + /// non-empty, non-default value should also set to + /// . + /// + /// + public string EditFormatString + { + get + { + return EditFormatStringProvider(); + } + set + { + EditFormatStringProvider = () => value; + } + } + + /// + /// Gets or sets a delegate which is used to get the edit format string for the model. See + /// . + /// + /// + /// + /// Setting also changes . + /// + /// + /// instances that set this property to a non-default value should + /// also set to . + /// + /// + public Func EditFormatStringProvider + { + get + { + return _editFormatStringProvider; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _editFormatStringProvider = value; + } + } + + /// + /// Gets the ordered and grouped display names and values of all values in + /// . See + /// . + /// + public IEnumerable> EnumGroupedDisplayNamesAndValues { get; set; } + + /// + /// Gets the names and values of all values in + /// . See . + /// + // This could be implemented in DefaultModelMetadata. But value should be cached. + public IReadOnlyDictionary EnumNamesAndValues { get; set; } + + /// + /// Gets or sets a value indicating whether or not the model has a non-default edit format. + /// See + /// + public bool HasNonDefaultEditFormat { get; set; } + + /// + /// Gets or sets a value indicating if the surrounding HTML should be hidden. + /// See + /// + public bool HideSurroundingHtml { get; set; } + + /// + /// Gets or sets a value indicating if the model value should be HTML encoded. + /// See + /// + public bool HtmlEncode { get; set; } = true; + + /// + /// Gets a value indicating whether is for an + /// . See . + /// + // This could be implemented in DefaultModelMetadata. But value is needed in the details provider. + public bool IsEnum { get; set; } + + /// + /// Gets a value indicating whether is for an + /// with an associated . See + /// . + /// + // This could be implemented in DefaultModelMetadata. But value is needed in the details provider. + public bool IsFlagsEnum { get; set; } + + /// + /// Gets or sets the text to display when the model value is . + /// See + /// + /// + /// Setting also changes . + /// + public string NullDisplayText + { + get + { + return NullDisplayTextProvider(); + } + set + { + NullDisplayTextProvider = () => value; + } + } + + /// + /// Gets or sets a delegate which is used to get the text to display when the model is . + /// See . + /// + /// + /// Setting also changes . + /// + public Func NullDisplayTextProvider + { + get + { + return _nullDisplayTextProvider; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _nullDisplayTextProvider = value; + } + } + + /// + /// Gets or sets the order. + /// See + /// + public int Order { get; set; } = 10000; + + /// + /// Gets or sets a delegate which is used to get a value for the + /// model's placeholder text. See . + /// + public Func Placeholder { get; set; } + + /// + /// Gets or sets a value indicating whether or not to include in the model value in display. + /// See + /// + public bool ShowForDisplay { get; set; } = true; + + /// + /// Gets or sets a value indicating whether or not to include in the model value in an editor. + /// See + /// + public bool ShowForEdit { get; set; } = true; + + /// + /// Gets or sets a the property name of a model property to use for display. + /// See + /// + public string SimpleDisplayProperty { get; set; } + + /// + /// Gets or sets a hint for location of a display or editor template. + /// See + /// + public string TemplateHint { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadataProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadataProviderContext.cs new file mode 100644 index 0000000000..650583557e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadataProviderContext.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// A context for and . + /// + public class DisplayMetadataProviderContext + { + /// + /// Creates a new . + /// + /// The for the . + /// The attributes for the . + public DisplayMetadataProviderContext( + ModelMetadataIdentity key, + ModelAttributes attributes) + { + if (attributes == null) + { + throw new ArgumentNullException(nameof(attributes)); + } + + Key = key; + Attributes = attributes.Attributes; + PropertyAttributes = attributes.PropertyAttributes; + TypeAttributes = attributes.TypeAttributes; + + DisplayMetadata = new DisplayMetadata(); + } + + /// + /// Gets the attributes. + /// + public IReadOnlyList Attributes { get; } + + /// + /// Gets the . + /// + public DisplayMetadata DisplayMetadata { get; } + + /// + /// Gets the . + /// + public ModelMetadataIdentity Key { get; } + + /// + /// Gets the property attributes. + /// + public IReadOnlyList PropertyAttributes { get; } + + /// + /// Gets the type attributes. + /// + public IReadOnlyList TypeAttributes { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ExcludeBindingMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ExcludeBindingMetadataProvider.cs new file mode 100644 index 0000000000..ca171d0c7c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ExcludeBindingMetadataProvider.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.ModelBinding.Metadata +{ + /// + /// An which configures to + /// false for matching types. + /// + public class ExcludeBindingMetadataProvider : IBindingMetadataProvider + { + private readonly Type _type; + + /// + /// Creates a new for the given . + /// + /// + /// The . All properties with this will have + /// set to false. + /// + public ExcludeBindingMetadataProvider(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + _type = type; + } + + /// + public void CreateBindingMetadata(BindingMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // No-op if the metadata is not for the target type + if (!_type.IsAssignableFrom(context.Key.ModelType)) + { + return; + } + + context.BindingMetadata.IsBindingAllowed = false; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IBindingMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IBindingMetadataProvider.cs new file mode 100644 index 0000000000..654784db5f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IBindingMetadataProvider.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.ModelBinding.Metadata +{ + /// + /// Provides for a . + /// + public interface IBindingMetadataProvider : IMetadataDetailsProvider + { + /// + /// Sets the values for properties of . + /// + /// The . + void CreateBindingMetadata(BindingMetadataProviderContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ICompositeMetadataDetailsProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ICompositeMetadataDetailsProvider.cs new file mode 100644 index 0000000000..58813af161 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ICompositeMetadataDetailsProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// A composite . + /// + public interface ICompositeMetadataDetailsProvider : + IBindingMetadataProvider, + IDisplayMetadataProvider, + IValidationMetadataProvider + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IDisplayMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IDisplayMetadataProvider.cs new file mode 100644 index 0000000000..a6e063da2a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IDisplayMetadataProvider.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.ModelBinding.Metadata +{ + /// + /// Provides for a . + /// + public interface IDisplayMetadataProvider : IMetadataDetailsProvider + { + /// + /// Sets the values for properties of . + /// + /// The . + void CreateDisplayMetadata(DisplayMetadataProviderContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IMetadataDetailsProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IMetadataDetailsProvider.cs new file mode 100644 index 0000000000..143ceb4faf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IMetadataDetailsProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// Marker interface for a provider of metadata details about model objects. Implementations should + /// implement one or more of , , + /// and . + /// + public interface IMetadataDetailsProvider + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IModelMetadataProvider2.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IModelMetadataProvider2.cs new file mode 100644 index 0000000000..b9d8d809f4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IModelMetadataProvider2.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.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + internal interface IModelMetadataProvider2 + { + /// + /// Supplies metadata describing a parameter. + /// + /// The + /// The actual model type. + /// A instance describing the . + ModelMetadata GetMetadataForParameter(ParameterInfo parameter, Type modelType); + + /// + /// Supplies metadata describing a property. + /// + /// The . + /// The actual model type. + /// A instance describing the . + ModelMetadata GetMetadataForProperty(PropertyInfo propertyInfo, Type modelType); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IValidationMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IValidationMetadataProvider.cs new file mode 100644 index 0000000000..557ccc8c7b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IValidationMetadataProvider.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.ModelBinding.Metadata +{ + /// + /// Provides for a . + /// + public interface IValidationMetadataProvider : IMetadataDetailsProvider + { + /// + /// Gets the values for properties of . + /// + /// The . + void CreateValidationMetadata(ValidationMetadataProviderContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/MetadataDetailsProviderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/MetadataDetailsProviderExtensions.cs new file mode 100644 index 0000000000..dbb474956e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/MetadataDetailsProviderExtensions.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// Extension methods for . + /// + public static class MetadataDetailsProviderExtensions + { + /// + /// Removes all metadata details providers of the specified type. + /// + /// The list of s. + /// The type to remove. + public static void RemoveType(this IList list) where TMetadataDetailsProvider : IMetadataDetailsProvider + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + RemoveType(list, typeof(TMetadataDetailsProvider)); + } + + /// + /// Removes all metadata details providers of the specified type. + /// + /// The list of s. + /// The type to remove. + public static void RemoveType(this IList list, Type type) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + for (var i = list.Count - 1; i >= 0; i--) + { + var metadataDetailsProvider = list[i]; + if (metadataDetailsProvider.GetType() == type) + { + list.RemoveAt(i); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelAttributes.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelAttributes.cs new file mode 100644 index 0000000000..c5d48ed8a9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelAttributes.cs @@ -0,0 +1,260 @@ +// Copyright (c) .NET 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.ModelBinding +{ + /// + /// Provides access to the combined list of attributes associated with a , property, or parameter. + /// + public class ModelAttributes + { + private static readonly IEnumerable _emptyAttributesCollection = Enumerable.Empty(); + + /// + /// Creates a new for a . + /// + /// The set of attributes for the . + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative is " + nameof(ModelAttributes) + "." + nameof(GetAttributesForType) + ".")] + public ModelAttributes(IEnumerable typeAttributes) + : this(typeAttributes, null, null) + { + } + + /// + /// Creates a new for a property. + /// + /// The set of attributes for the property. + /// + /// The set of attributes for the property's . See . + /// + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative is " + nameof(ModelAttributes) + "." + nameof(GetAttributesForProperty) + ".")] + public ModelAttributes(IEnumerable propertyAttributes, IEnumerable typeAttributes) + : this(typeAttributes, propertyAttributes, null) + { + } + + /// + /// Creates a new . + /// + /// + /// If this instance represents a type, the set of attributes for that type. + /// If this instance represents a property, the set of attributes for the property's . + /// Otherwise, null. + /// + /// + /// If this instance represents a property, the set of attributes for that property. + /// Otherwise, null. + /// + /// + /// If this instance represents a parameter, the set of attributes for that parameter. + /// Otherwise, null. + /// + internal ModelAttributes( + IEnumerable typeAttributes, + IEnumerable propertyAttributes, + IEnumerable parameterAttributes) + { + if (propertyAttributes != null) + { + // Represents a property + if (typeAttributes == null) + { + throw new ArgumentNullException(nameof(typeAttributes)); + } + + PropertyAttributes = propertyAttributes.ToArray(); + TypeAttributes = typeAttributes.ToArray(); + Attributes = PropertyAttributes.Concat(TypeAttributes).ToArray(); + } + else if (parameterAttributes != null) + { + // Represents a parameter + if (typeAttributes == null) + { + throw new ArgumentNullException(nameof(typeAttributes)); + } + + ParameterAttributes = parameterAttributes.ToArray(); + TypeAttributes = typeAttributes.ToArray(); + Attributes = ParameterAttributes.Concat(TypeAttributes).ToArray(); + } + else if (typeAttributes != null) + { + // Represents a type + if (typeAttributes == null) + { + throw new ArgumentNullException(nameof(typeAttributes)); + } + + Attributes = TypeAttributes = typeAttributes.ToArray(); + } + } + + /// + /// Gets the set of all attributes. If this instance represents the attributes for a property, the attributes + /// on the property definition are before those on the property's . If this instance + /// represents the attributes for a parameter, the attributes on the parameter definition are before those on + /// the parameter's . + /// + public IReadOnlyList Attributes { get; } + + /// + /// Gets the set of attributes on the property, or null if this instance does not represent the attributes + /// for a property. + /// + public IReadOnlyList PropertyAttributes { get; } + + /// + /// Gets the set of attributes on the parameter, or null if this instance does not represent the attributes + /// for a parameter. + /// + public IReadOnlyList ParameterAttributes { get; } + + /// + /// Gets the set of attributes on the . If this instance represents a property, then + /// contains attributes retrieved from . + /// If this instance represents a parameter, then contains attributes retrieved from + /// . + /// + public IReadOnlyList TypeAttributes { get; } + + /// + /// Gets the attributes for the given . + /// + /// The in which caller found . + /// + /// A for which attributes need to be resolved. + /// + /// + /// A instance with the attributes of the property and its . + /// + public static ModelAttributes GetAttributesForProperty(Type type, PropertyInfo property) + { + 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 . + /// + internal static ModelAttributes GetAttributesForProperty(Type containerType, PropertyInfo property, Type modelType) + { + if (containerType == null) + { + throw new ArgumentNullException(nameof(containerType)); + } + + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + var propertyAttributes = property.GetCustomAttributes(); + var typeAttributes = modelType.GetCustomAttributes(); + + var metadataType = GetMetadataType(containerType); + if (metadataType != null) + { + var metadataProperty = metadataType.GetRuntimeProperty(property.Name); + if (metadataProperty != null) + { + propertyAttributes = propertyAttributes.Concat(metadataProperty.GetCustomAttributes()); + } + } + + return new ModelAttributes(typeAttributes, propertyAttributes, parameterAttributes: null); + } + + /// + /// Gets the attributes for the given . + /// + /// The for which attributes need to be resolved. + /// + /// A instance with the attributes of the . + public static ModelAttributes GetAttributesForType(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var attributes = type.GetCustomAttributes(); + + var metadataType = GetMetadataType(type); + if (metadataType != null) + { + attributes = attributes.Concat(metadataType.GetCustomAttributes()); + } + + return new ModelAttributes(attributes, propertyAttributes: null, parameterAttributes: null); + } + + /// + /// Gets the attributes for the given . + /// + /// + /// The for which attributes need to be resolved. + /// + /// + /// A instance with the attributes of the parameter and its . + /// + public static ModelAttributes GetAttributesForParameter(ParameterInfo parameterInfo) + { + // 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(parameterInfo.ParameterType).TypeAttributes; + var parameterAttributes = parameterInfo.GetCustomAttributes(); + + 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 . + /// + internal 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.GetCustomAttribute()?.MetadataType; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadata.cs new file mode 100644 index 0000000000..bd4ff327aa --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadata.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET 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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// Validation metadata details for a . + /// + public class ValidationMetadata + { + /// + /// Gets or sets a value indicating whether or not the model is a required value. Will be ignored + /// if the model metadata being created is not a property. If null then + /// will be computed based on the model . + /// See . + /// + public bool? IsRequired { get; set; } + + /// + /// Gets or sets an implementation that indicates whether this model + /// should be validated. See . + /// + public IPropertyValidationFilter PropertyValidationFilter { get; set; } + + /// + /// Gets or sets a value that indicates whether children of the model should be validated. If null + /// then will be true if either of + /// or is true; + /// false otherwise. + /// + public bool? ValidateChildren { get; set; } + + /// + /// Gets a list of metadata items for validators. + /// + /// + /// implementations should store metadata items + /// in this list, to be consumed later by an . + /// + public IList ValidatorMetadata { get; } = new List(); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadataProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadataProviderContext.cs new file mode 100644 index 0000000000..84cfdd76bf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadataProviderContext.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// A context for an . + /// + public class ValidationMetadataProviderContext + { + /// + /// Creates a new . + /// + /// The for the . + /// The attributes for the . + public ValidationMetadataProviderContext( + ModelMetadataIdentity key, + ModelAttributes attributes) + { + if (attributes == null) + { + throw new ArgumentNullException(nameof(attributes)); + } + + Key = key; + Attributes = attributes.Attributes; + PropertyAttributes = attributes.PropertyAttributes; + TypeAttributes = attributes.TypeAttributes; + + ValidationMetadata = new ValidationMetadata(); + } + + /// + /// Gets the attributes. + /// + public IReadOnlyList Attributes { get; } + + /// + /// Gets the . + /// + public ModelMetadataIdentity Key { get; } + + /// + /// Gets the property attributes. + /// + public IReadOnlyList PropertyAttributes { get; } + + /// + /// Gets the type attributes. + /// + public IReadOnlyList TypeAttributes { get; } + + /// + /// Gets the . + /// + public ValidationMetadata ValidationMetadata { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs new file mode 100644 index 0000000000..216fc7c803 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs @@ -0,0 +1,365 @@ +// Copyright (c) .NET 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A factory for instances. + /// + public class ModelBinderFactory : IModelBinderFactory + { + private readonly IModelMetadataProvider _metadataProvider; + private readonly IModelBinderProvider[] _providers; + private readonly ConcurrentDictionary _cache; + private readonly IServiceProvider _serviceProvider; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that also takes an . + /// Creates a new . + /// + /// The . + /// The for . + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that also takes an " + nameof(IServiceProvider) + ".")] + public ModelBinderFactory(IModelMetadataProvider metadataProvider, IOptions options) + : this(metadataProvider, options, GetDefaultServices()) + { + } + + /// + /// Creates a new . + /// + /// The . + /// The for . + /// The . + public ModelBinderFactory( + IModelMetadataProvider metadataProvider, + IOptions options, + IServiceProvider serviceProvider) + { + _metadataProvider = metadataProvider; + _providers = options.Value.ModelBinderProviders.ToArray(); + _serviceProvider = serviceProvider; + _cache = new ConcurrentDictionary(); + + var loggerFactory = serviceProvider.GetRequiredService(); + var logger = loggerFactory.CreateLogger(); + logger.RegisteredModelBinderProviders(_providers); + } + + /// + public IModelBinder CreateBinder(ModelBinderFactoryContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (_providers.Length == 0) + { + throw new InvalidOperationException(Resources.FormatModelBinderProvidersAreRequired( + typeof(MvcOptions).FullName, + nameof(MvcOptions.ModelBinderProviders), + typeof(IModelBinderProvider).FullName)); + } + + if (TryGetCachedBinder(context.Metadata, context.CacheToken, out var binder)) + { + return binder; + } + + // Perf: We're calling the Uncached version of the API here so we can: + // 1. avoid allocating a context when the value is already cached + // 2. avoid checking the cache twice when the value is not cached + var providerContext = new DefaultModelBinderProviderContext(this, context); + binder = CreateBinderCoreUncached(providerContext, context.CacheToken); + if (binder == null) + { + var message = Resources.FormatCouldNotCreateIModelBinder(providerContext.Metadata.ModelType); + throw new InvalidOperationException(message); + } + + Debug.Assert(!(binder is PlaceholderBinder)); + AddToCache(context.Metadata, context.CacheToken, binder); + + return binder; + } + + // Called by the DefaultModelBinderProviderContext when we're recursively creating a binder + // so that all intermediate results can be cached. + private IModelBinder CreateBinderCoreCached(DefaultModelBinderProviderContext providerContext, object token) + { + if (TryGetCachedBinder(providerContext.Metadata, token, out var binder)) + { + return binder; + } + + // We're definitely creating a binder for an non-root node here, so it's OK for binder creation + // to fail. + binder = CreateBinderCoreUncached(providerContext, token) ?? NoOpBinder.Instance; + + if (!(binder is PlaceholderBinder)) + { + AddToCache(providerContext.Metadata, token, binder); + } + + return binder; + } + + private IModelBinder CreateBinderCoreUncached(DefaultModelBinderProviderContext providerContext, object token) + { + if (!providerContext.Metadata.IsBindingAllowed) + { + return NoOpBinder.Instance; + } + + // A non-null token will usually be passed in at the top level (ParameterDescriptor likely). + // This prevents us from treating a parameter the same as a collection-element - which could + // happen looking at just model metadata. + var key = new Key(providerContext.Metadata, token); + + // The providerContext.Visited is used here to break cycles in recursion. We need a separate + // per-operation cache for cycle breaking because the global cache (_cache) needs to always stay + // in a valid state. + // + // We store null as a sentinel inside the providerContext.Visited to track the fact that we've visited + // a given node but haven't yet created a binder for it. We don't want to eagerly create a + // PlaceholderBinder because that would result in lots of unnecessary indirection and allocations. + var visited = providerContext.Visited; + + if (visited.TryGetValue(key, out var binder)) + { + if (binder != null) + { + return binder; + } + + // If we're currently recursively building a binder for this type, just return + // a PlaceholderBinder. We'll fix it up later to point to the 'real' binder + // when the stack unwinds. + binder = new PlaceholderBinder(); + visited[key] = binder; + return binder; + } + + // OK this isn't a recursive case (yet) so add an entry and then ask the providers + // to create the binder. + visited.Add(key, null); + + IModelBinder result = null; + + for (var i = 0; i < _providers.Length; i++) + { + var provider = _providers[i]; + result = provider.GetBinder(providerContext); + if (result != null) + { + break; + } + } + + // If the PlaceholderBinder was created, then it means we recursed. Hook it up to the 'real' binder. + if (visited[key] is PlaceholderBinder placeholderBinder) + { + // It's also possible that user code called into `CreateBinder` but then returned null, we don't + // want to create something that will null-ref later so just hook this up to the no-op binder. + placeholderBinder.Inner = result ?? NoOpBinder.Instance; + } + + if (result != null) + { + visited[key] = result; + } + + return result; + } + + private void AddToCache(ModelMetadata metadata, object cacheToken, IModelBinder binder) + { + Debug.Assert(metadata != null); + Debug.Assert(binder != null); + + if (cacheToken == null) + { + return; + } + + _cache.TryAdd(new Key(metadata, cacheToken), binder); + } + + private bool TryGetCachedBinder(ModelMetadata metadata, object cacheToken, out IModelBinder binder) + { + Debug.Assert(metadata != null); + + if (cacheToken == null) + { + binder = null; + return false; + } + + return _cache.TryGetValue(new Key(metadata, cacheToken), out binder); + } + + private static IServiceProvider GetDefaultServices() + { + var services = new ServiceCollection(); + services.AddSingleton(NullLoggerFactory.Instance); + return services.BuildServiceProvider(); + } + + private class DefaultModelBinderProviderContext : ModelBinderProviderContext + { + private readonly ModelBinderFactory _factory; + + public DefaultModelBinderProviderContext( + ModelBinderFactory factory, + ModelBinderFactoryContext factoryContext) + { + _factory = factory; + Metadata = factoryContext.Metadata; + BindingInfo bindingInfo; + if (factoryContext.BindingInfo != null) + { + bindingInfo = new BindingInfo(factoryContext.BindingInfo); + } + else + { + bindingInfo = new BindingInfo(); + } + + bindingInfo.TryApplyBindingInfo(Metadata); + BindingInfo = bindingInfo; + + MetadataProvider = _factory._metadataProvider; + Visited = new Dictionary(); + } + + private DefaultModelBinderProviderContext( + DefaultModelBinderProviderContext parent, + ModelMetadata metadata, + BindingInfo bindingInfo) + { + Metadata = metadata; + + _factory = parent._factory; + MetadataProvider = parent.MetadataProvider; + Visited = parent.Visited; + BindingInfo = bindingInfo; + } + + public override BindingInfo BindingInfo { get; } + + public override ModelMetadata Metadata { get; } + + public override IModelMetadataProvider MetadataProvider { get; } + + public Dictionary Visited { get; } + + public override IServiceProvider Services => _factory._serviceProvider; + + public override IModelBinder CreateBinder(ModelMetadata metadata) + { + return CreateBinder( + metadata, + new BindingInfo() + { + BinderModelName = metadata.BinderModelName, + BinderType = metadata.BinderType, + BindingSource = metadata.BindingSource, + PropertyFilterProvider = metadata.PropertyFilterProvider, + }); + } + + public override IModelBinder CreateBinder(ModelMetadata metadata, BindingInfo bindingInfo) + { + if (metadata == null) + { + throw new ArgumentNullException(nameof(metadata)); + } + + if (bindingInfo == null) + { + throw new ArgumentNullException(nameof(bindingInfo)); + } + + // For non-root nodes we use the ModelMetadata as the cache token. This ensures that all non-root + // nodes with the same metadata will have the same binder. This is OK because for an non-root + // node there's no opportunity to customize binding info like there is for a parameter. + var token = metadata; + + var nestedContext = new DefaultModelBinderProviderContext(this, metadata, bindingInfo); + return _factory.CreateBinderCoreCached(nestedContext, token); + } + } + + // This key allows you to specify a ModelMetadata which represents the type/property being bound + // and a 'token' which acts as an arbitrary discriminator. + // + // This is necessary because the same metadata might be bound as a top-level parameter (with BindingInfo on + // 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 ModelMetadata _metadata; + private readonly object _token; // Explicitly using ReferenceEquality for tokens. + + public Key(ModelMetadata metadata, object token) + { + _metadata = metadata; + _token = token; + } + + public bool Equals(Key other) + { + return _metadata.Equals(other._metadata) && object.ReferenceEquals(_token, other._token); + } + + public override bool Equals(object obj) + { + var other = obj as Key?; + return other.HasValue && Equals(other.Value); + } + + public override int GetHashCode() + { + var hash = new HashCodeCombiner(); + hash.Add(_metadata); + hash.Add(RuntimeHelpers.GetHashCode(_token)); + return hash; + } + + public override string ToString() + { + switch (_metadata.MetadataKind) + { + case ModelMetadataKind.Parameter: + return $"{_token} (Parameter: '{_metadata.ParameterName}' Type: '{_metadata.ModelType.Name}')"; + case ModelMetadataKind.Property: + return $"{_token} (Property: '{_metadata.ContainerType.Name}.{_metadata.PropertyName}' " + + $"Type: '{_metadata.ModelType.Name}')"; + case ModelMetadataKind.Type: + return $"{_token} (Type: '{_metadata.ModelType.Name}')"; + default: + return $"Unsupported MetadataKind '{_metadata.MetadataKind}'."; + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactoryContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactoryContext.cs new file mode 100644 index 0000000000..3ed2b371a4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactoryContext.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.ModelBinding +{ + /// + /// A context object for . + /// + public class ModelBinderFactoryContext + { + /// + /// Gets or sets the . + /// + public BindingInfo BindingInfo { get; set; } + + /// + /// Gets or sets the . + /// + public ModelMetadata Metadata { get; set; } + + /// + /// Gets or sets the cache token. If non-null the resulting + /// will be cached. + /// + public object CacheToken { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderProviderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderProviderExtensions.cs new file mode 100644 index 0000000000..791959a06a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderProviderExtensions.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Extension methods for . + /// + public static class ModelBinderProviderExtensions + { + /// + /// Removes all model binder providers of the specified type. + /// + /// The list of s. + /// The type to remove. + public static void RemoveType(this IList list) where TModelBinderProvider : IModelBinderProvider + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + RemoveType(list, typeof(TModelBinderProvider)); + } + + /// + /// Removes all model binder providers of the specified type. + /// + /// The list of s. + /// The type to remove. + public static void RemoveType(this IList list, Type type) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + for (var i = list.Count - 1; i >= 0; i--) + { + var modelBinderProvider = list[i]; + if (modelBinderProvider.GetType() == type) + { + list.RemoveAt(i); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelMetadataProviderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelMetadataProviderExtensions.cs new file mode 100644 index 0000000000..ab2c2ab3d4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelMetadataProviderExtensions.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; +using Microsoft.AspNetCore.Mvc.Core; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Extensions methods for . + /// + public static class ModelMetadataProviderExtensions + { + /// + /// Gets a for property identified by the provided + /// and . + /// + /// The . + /// The for which the property is defined. + /// The property name. + /// A for the property. + public static ModelMetadata GetMetadataForProperty( + this IModelMetadataProvider provider, + Type containerType, + string propertyName) + { + if (provider == null) + { + throw new ArgumentNullException(nameof(provider)); + } + + if (containerType == null) + { + throw new ArgumentNullException(nameof(containerType)); + } + + if (propertyName == null) + { + throw new ArgumentNullException(nameof(propertyName)); + } + + var containerMetadata = provider.GetMetadataForType(containerType); + + var propertyMetadata = containerMetadata.Properties[propertyName]; + if (propertyMetadata == null) + { + var message = Resources.FormatCommon_PropertyNotFound(containerType, propertyName); + throw new ArgumentException(message, nameof(propertyName)); + } + + return propertyMetadata; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelNames.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelNames.cs new file mode 100644 index 0000000000..185c8345e0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelNames.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.Globalization; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + public static class ModelNames + { + public static string CreateIndexModelName(string parentName, int index) + { + return CreateIndexModelName(parentName, index.ToString(CultureInfo.InvariantCulture)); + } + + public static string CreateIndexModelName(string parentName, string index) + { + return (parentName.Length == 0) ? "[" + index + "]" : parentName + "[" + index + "]"; + } + + public static string CreatePropertyModelName(string prefix, string propertyName) + { + if (string.IsNullOrEmpty(prefix)) + { + return propertyName ?? string.Empty; + } + + if (string.IsNullOrEmpty(propertyName)) + { + return prefix ?? string.Empty; + } + + if (propertyName.StartsWith("[", StringComparison.Ordinal)) + { + // The propertyName might represent an indexer access, in which case combining + // with a 'dot' would be invalid. This case occurs only when called from ValidationVisitor. + return prefix + propertyName; + } + + return prefix + "." + propertyName; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ObjectModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ObjectModelValidator.cs new file mode 100644 index 0000000000..0b89489a5f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ObjectModelValidator.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET 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.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Provides a base implementation for validating an object graph. + /// + public abstract class ObjectModelValidator : IObjectModelValidator + { + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly ValidatorCache _validatorCache; + private readonly CompositeModelValidatorProvider _validatorProvider; + + /// + /// Initializes a new instance of . + /// + /// The . + /// The list of . + public ObjectModelValidator( + IModelMetadataProvider modelMetadataProvider, + IList validatorProviders) + { + if (modelMetadataProvider == null) + { + throw new ArgumentNullException(nameof(modelMetadataProvider)); + } + + if (validatorProviders == null) + { + throw new ArgumentNullException(nameof(validatorProviders)); + } + + _modelMetadataProvider = modelMetadataProvider; + _validatorCache = new ValidatorCache(); + + _validatorProvider = new CompositeModelValidatorProvider(validatorProviders); + } + + /// + public virtual void Validate( + ActionContext actionContext, + ValidationStateDictionary validationState, + string prefix, + object model) + { + var visitor = GetValidationVisitor( + actionContext, + _validatorProvider, + _validatorCache, + _modelMetadataProvider, + validationState); + + var metadata = model == null ? null : _modelMetadataProvider.GetMetadataForType(model.GetType()); + visitor.Validate(metadata, prefix, model, alwaysValidateAtTopLevel: false); + } + + /// + /// Validates the provided object model. + /// If is and the 's + /// is , will add one or more + /// model state errors that + /// would not. + /// + /// The . + /// The . + /// The model prefix key. + /// The model object. + /// The . + public virtual void Validate( + ActionContext actionContext, + ValidationStateDictionary validationState, + string prefix, + object model, + ModelMetadata metadata) + { + var visitor = GetValidationVisitor( + actionContext, + _validatorProvider, + _validatorCache, + _modelMetadataProvider, + validationState); + + visitor.Validate(metadata, prefix, model, alwaysValidateAtTopLevel: metadata.IsRequired); + } + + /// + /// Gets a that traverses the object model graph and performs validation. + /// + /// The . + /// The . + /// The . + /// The . + /// The . + /// A which traverses the object model graph. + public abstract ValidationVisitor GetValidationVisitor( + ActionContext actionContext, + IModelValidatorProvider validatorProvider, + ValidatorCache validatorCache, + IModelMetadataProvider metadataProvider, + ValidationStateDictionary validationState); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs new file mode 100644 index 0000000000..7a04fe8be3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs @@ -0,0 +1,371 @@ +// Copyright (c) .NET 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.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Binds and validates models specified by a . + /// + public class ParameterBinder + { + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly IModelBinderFactory _modelBinderFactory; + private readonly MvcOptions _mvcOptions; + private readonly IObjectModelValidator _objectModelValidator; + + /// + /// This constructor is obsolete and will be removed in a future version. The recommended alternative + /// is the overload that also takes a accessor and an . + /// + /// Initializes a new instance of . + /// + /// The . + /// The . + /// The . + [Obsolete("This constructor is obsolete and will be removed in a future version. The recommended alternative" + + " is the overload that also takes a " + nameof(MvcOptions) + " accessor and an " + + nameof(ILoggerFactory) + " .")] + public ParameterBinder( + IModelMetadataProvider modelMetadataProvider, + IModelBinderFactory modelBinderFactory, + IObjectModelValidator validator) + : this( + modelMetadataProvider, + modelBinderFactory, + validator, + Options.Create(new MvcOptions()), + NullLoggerFactory.Instance) + { + } + + /// + /// Initializes a new instance of . + /// + /// The . + /// The . + /// The . + /// The accessor. + /// The . + public ParameterBinder( + IModelMetadataProvider modelMetadataProvider, + IModelBinderFactory modelBinderFactory, + IObjectModelValidator validator, + IOptions mvcOptions, + ILoggerFactory loggerFactory) + { + if (modelMetadataProvider == null) + { + throw new ArgumentNullException(nameof(modelMetadataProvider)); + } + + if (modelBinderFactory == null) + { + throw new ArgumentNullException(nameof(modelBinderFactory)); + } + + if (validator == null) + { + throw new ArgumentNullException(nameof(validator)); + } + + if (mvcOptions == null) + { + throw new ArgumentNullException(nameof(mvcOptions)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _modelMetadataProvider = modelMetadataProvider; + _modelBinderFactory = modelBinderFactory; + _objectModelValidator = validator; + _mvcOptions = mvcOptions.Value; + Logger = loggerFactory.CreateLogger(GetType()); + } + + /// + /// The used for logging in this binder. + /// + protected ILogger Logger { get; } + + /// + /// Initializes and binds a model specified by . + /// + /// The . + /// The . + /// The + /// The result of model binding. + public Task BindModelAsync( + ActionContext actionContext, + IValueProvider valueProvider, + ParameterDescriptor parameter) + { + return BindModelAsync(actionContext, valueProvider, parameter, value: null); + } + + /// + /// Binds a model specified by using as the initial value. + /// + /// The . + /// The . + /// The + /// The initial model value. + /// The result of model binding. + public virtual Task BindModelAsync( + ActionContext actionContext, + IValueProvider valueProvider, + ParameterDescriptor parameter, + object value) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (parameter == null) + { + throw new ArgumentNullException(nameof(parameter)); + } + + var metadata = _modelMetadataProvider.GetMetadataForType(parameter.ParameterType); + var binder = _modelBinderFactory.CreateBinder(new ModelBinderFactoryContext + { + BindingInfo = parameter.BindingInfo, + Metadata = metadata, + CacheToken = parameter, + }); + + return BindModelAsync( + actionContext, + binder, + valueProvider, + parameter, + metadata, + value); + } + + /// + /// Binds a model specified by using as the initial value. + /// + /// The . + /// The . + /// The . + /// The + /// The . + /// The initial model value. + /// The result of model binding. + public virtual async Task BindModelAsync( + ActionContext actionContext, + IModelBinder modelBinder, + IValueProvider valueProvider, + ParameterDescriptor parameter, + ModelMetadata metadata, + object value) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + if (modelBinder == null) + { + throw new ArgumentNullException(nameof(modelBinder)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (parameter == null) + { + throw new ArgumentNullException(nameof(parameter)); + } + + if (metadata == null) + { + throw new ArgumentNullException(nameof(metadata)); + } + + if (parameter.BindingInfo?.RequestPredicate?.Invoke(actionContext) == false) + { + return ModelBindingResult.Failed(); + } + + var modelBindingContext = DefaultModelBindingContext.CreateBindingContext( + actionContext, + valueProvider, + metadata, + parameter.BindingInfo, + parameter.Name); + modelBindingContext.Model = value; + + Logger.AttemptingToBindParameterOrProperty(parameter, modelBindingContext); + + var parameterModelName = parameter.BindingInfo?.BinderModelName ?? metadata.BinderModelName; + if (parameterModelName != null) + { + // The name was set explicitly, always use that as the prefix. + modelBindingContext.ModelName = parameterModelName; + } + else if (modelBindingContext.ValueProvider.ContainsPrefix(parameter.Name)) + { + // We have a match for the parameter name, use that as that prefix. + modelBindingContext.ModelName = parameter.Name; + } + else + { + // No match, fallback to empty string as the prefix. + modelBindingContext.ModelName = string.Empty; + } + + await modelBinder.BindModelAsync(modelBindingContext); + + Logger.DoneAttemptingToBindParameterOrProperty(parameter, modelBindingContext); + + var modelBindingResult = modelBindingContext.Result; + + if (_mvcOptions.AllowValidatingTopLevelNodes && + _objectModelValidator is ObjectModelValidator baseObjectValidator) + { + Logger.AttemptingToValidateParameterOrProperty(parameter, modelBindingContext); + + EnforceBindRequiredAndValidate( + baseObjectValidator, + actionContext, + parameter, + metadata, + modelBindingContext, + modelBindingResult); + + Logger.DoneAttemptingToValidateParameterOrProperty(parameter, modelBindingContext); + } + else + { + // For legacy implementations (which directly implemented IObjectModelValidator), fall back to the + // back-compatibility logic. In this scenario, top-level validation attributes will be ignored like + // they were historically. + if (modelBindingResult.IsModelSet) + { + _objectModelValidator.Validate( + actionContext, + modelBindingContext.ValidationState, + modelBindingContext.ModelName, + modelBindingResult.Model); + } + } + + return modelBindingResult; + } + + private void EnforceBindRequiredAndValidate( + ObjectModelValidator baseObjectValidator, + ActionContext actionContext, + ParameterDescriptor parameter, + ModelMetadata metadata, + ModelBindingContext modelBindingContext, + ModelBindingResult modelBindingResult) + { + RecalculateModelMetadata(parameter, modelBindingResult, ref metadata); + + if (!modelBindingResult.IsModelSet && metadata.IsBindingRequired) + { + // Enforce BindingBehavior.Required (e.g., [BindRequired]) + var modelName = modelBindingContext.FieldName; + var message = metadata.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(modelName); + actionContext.ModelState.TryAddModelError(modelName, message); + } + else if (modelBindingResult.IsModelSet) + { + // Enforce any other validation rules + baseObjectValidator.Validate( + actionContext, + modelBindingContext.ValidationState, + modelBindingContext.ModelName, + modelBindingResult.Model, + metadata); + } + else if (metadata.IsRequired) + { + // We need to special case the model name for cases where a 'fallback' to empty + // prefix occurred but binding wasn't successful. For these cases there will be no + // entry in validation state to match and determine the correct key. + // + // See https://github.com/aspnet/Mvc/issues/7503 + // + // This is to avoid adding validation errors for an 'empty' prefix when a simple + // type fails to bind. The fix for #7503 uncovered this issue, and was likely the + // original problem being worked around that regressed #7503. + var modelName = modelBindingContext.ModelName; + + if (string.IsNullOrEmpty(modelBindingContext.ModelName) && + parameter.BindingInfo?.BinderModelName == null) + { + // If we get here then this is a fallback case. The model name wasn't explicitly set + // and we ended up with an empty prefix. + modelName = modelBindingContext.FieldName; + } + + // Run validation, we expect this to validate [Required]. + baseObjectValidator.Validate( + actionContext, + modelBindingContext.ValidationState, + modelName, + modelBindingResult.Model, + 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 IModelMetadataProvider2 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/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs new file mode 100644 index 0000000000..a905d72752 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs @@ -0,0 +1,98 @@ +// Copyright (c) .NET 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.Http; +using Microsoft.AspNetCore.Mvc.Internal; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An adapter for data stored in an . + /// + public class QueryStringValueProvider : BindingSourceValueProvider, IEnumerableValueProvider + { + private readonly CultureInfo _culture; + private readonly IQueryCollection _values; + private PrefixContainer _prefixContainer; + + /// + /// Creates a value provider for . + /// + /// The for the data. + /// The key value pairs to wrap. + /// The culture to return with ValueProviderResult instances. + public QueryStringValueProvider( + BindingSource bindingSource, + IQueryCollection values, + CultureInfo culture) + : base(bindingSource) + { + if (bindingSource == null) + { + throw new ArgumentNullException(nameof(bindingSource)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + _values = values; + _culture = culture; + } + + public CultureInfo Culture => _culture; + + protected PrefixContainer PrefixContainer + { + get + { + if (_prefixContainer == null) + { + _prefixContainer = new PrefixContainer(_values.Keys); + } + + return _prefixContainer; + } + } + + /// + public override bool ContainsPrefix(string prefix) + { + return PrefixContainer.ContainsPrefix(prefix); + } + + /// + public virtual IDictionary GetKeysFromPrefix(string prefix) + { + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + return PrefixContainer.GetKeysFromPrefix(prefix); + } + + /// + public override ValueProviderResult GetValue(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + var values = _values[key]; + if (values.Count == 0) + { + return ValueProviderResult.None; + } + else + { + return new ValueProviderResult(values, _culture); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProviderFactory.cs new file mode 100644 index 0000000000..b6e620df82 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProviderFactory.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.Globalization; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A that creates instances that + /// read values from the request query-string. + /// + public class QueryStringValueProviderFactory : IValueProviderFactory + { + /// + public Task CreateValueProviderAsync(ValueProviderFactoryContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var query = context.ActionContext.HttpContext.Request.Query; + if (query != null && query.Count > 0) + { + var valueProvider = new QueryStringValueProvider( + BindingSource.Query, + query, + CultureInfo.InvariantCulture); + + context.ValueProviders.Add(valueProvider); + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProvider.cs new file mode 100644 index 0000000000..72f556c1c0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProvider.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.Globalization; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An adapter for data stored in an . + /// + public class RouteValueProvider : BindingSourceValueProvider + { + private readonly RouteValueDictionary _values; + private PrefixContainer _prefixContainer; + + /// + /// Creates a new . + /// + /// The of the data. + /// The values. + /// Sets to . + public RouteValueProvider( + BindingSource bindingSource, + RouteValueDictionary values) + : this(bindingSource, values, CultureInfo.InvariantCulture) + { + } + + /// + /// Creates a new . + /// + /// The of the data. + /// The values. + /// The culture for route value. + public RouteValueProvider(BindingSource bindingSource, RouteValueDictionary values, CultureInfo culture) + : base(bindingSource) + { + if (bindingSource == null) + { + throw new ArgumentNullException(nameof(bindingSource)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + if (culture == null) + { + throw new ArgumentNullException(nameof(culture)); + } + + _values = values; + Culture = culture; + } + + protected PrefixContainer PrefixContainer + { + get + { + if (_prefixContainer == null) + { + _prefixContainer = new PrefixContainer(_values.Keys); + } + + return _prefixContainer; + } + } + + protected CultureInfo Culture { get; } + + /// + public override bool ContainsPrefix(string key) + { + return PrefixContainer.ContainsPrefix(key); + } + + /// + public override ValueProviderResult GetValue(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + object value; + if (_values.TryGetValue(key, out value)) + { + var stringValue = value as string ?? value?.ToString() ?? string.Empty; + return new ValueProviderResult(stringValue, Culture); + } + else + { + return ValueProviderResult.None; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProviderFactory.cs new file mode 100644 index 0000000000..c87ca932dc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProviderFactory.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; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A for creating instances. + /// + public class RouteValueProviderFactory : IValueProviderFactory + { + /// + public Task CreateValueProviderAsync(ValueProviderFactoryContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var valueProvider = new RouteValueProvider( + BindingSource.Path, + context.ActionContext.RouteData.Values); + + context.ValueProviders.Add(valueProvider); + + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/SuppressChildValidationMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/SuppressChildValidationMetadataProvider.cs new file mode 100644 index 0000000000..5c10bfdcb3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/SuppressChildValidationMetadataProvider.cs @@ -0,0 +1,109 @@ +// Copyright (c) .NET 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.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// An which configures to + /// false for matching types. + /// + public class SuppressChildValidationMetadataProvider : IValidationMetadataProvider + { + /// + /// Creates a new for the given . + /// + /// + /// The . This and all assignable values will have + /// set to false. + /// + public SuppressChildValidationMetadataProvider(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + Type = type; + } + + /// + /// Creates a new for the given . + /// + /// + /// The type full name. This type and all of its subclasses will have + /// set to false. + /// + public SuppressChildValidationMetadataProvider(string fullTypeName) + { + if (fullTypeName == null) + { + throw new ArgumentNullException(nameof(fullTypeName)); + } + + FullTypeName = fullTypeName; + } + + /// + /// Gets the for which to suppress validation of children. + /// + public Type Type { get; } + + /// + /// Gets the full name of a type for which to suppress validation of children. + /// + public string FullTypeName { get; } + + /// + public void CreateValidationMetadata(ValidationMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (Type != null) + { + if (Type.IsAssignableFrom(context.Key.ModelType)) + { + context.ValidationMetadata.ValidateChildren = false; + } + + return; + } + + if (FullTypeName != null) + { + if (IsMatchingName(context.Key.ModelType)) + { + context.ValidationMetadata.ValidateChildren = false; + } + + return; + } + + Debug.Fail("We shouldn't get here."); + } + + private bool IsMatchingName(Type type) + { + Debug.Assert(FullTypeName != null); + + if (type == null) + { + return false; + } + + if (string.Equals(type.FullName, FullTypeName, StringComparison.Ordinal)) + { + return true; + } + + return IsMatchingName(type.GetTypeInfo().BaseType); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeException.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeException.cs new file mode 100644 index 0000000000..d9c1000af9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeException.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 System; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// The that is added to model state when a model binder for the body of the request is + /// unable to understand the request content type header. + /// + public class UnsupportedContentTypeException : Exception + { + /// + /// Creates a new instance of with the specified + /// exception . + /// + /// The message that describes the error. + public UnsupportedContentTypeException(string message) + : base(message) + { + if (message == null) + { + throw new ArgumentNullException(nameof(message)); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs new file mode 100644 index 0000000000..0c134d7dc9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.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.Filters; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// A filter that scans for in the + /// and short-circuits the pipeline + /// with an Unsupported Media Type (415) response. + /// + public class UnsupportedContentTypeFilter : IActionFilter + { + /// + public void OnActionExecuting(ActionExecutingContext context) + { + if (HasUnsupportedContentTypeError(context)) + { + context.Result = new UnsupportedMediaTypeResult(); + } + } + + private bool HasUnsupportedContentTypeError(ActionExecutingContext context) + { + var modelState = context.ModelState; + if (modelState.IsValid) + { + return false; + } + + foreach (var kvp in modelState) + { + var errors = kvp.Value.Errors; + for (int i = 0; i < errors.Count; i++) + { + var error = errors[i]; + if (error.Exception is UnsupportedContentTypeException) + { + return true; + } + } + } + + return false; + } + + /// + public void OnActionExecuted(ActionExecutedContext context) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeClientModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeClientModelValidatorProvider.cs new file mode 100644 index 0000000000..e485b10ef0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeClientModelValidatorProvider.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; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// Aggregate of s that delegates to its underlying providers. + /// + public class CompositeClientModelValidatorProvider : IClientModelValidatorProvider + { + /// + /// Initializes a new instance of . + /// + /// + /// A collection of instances. + /// + public CompositeClientModelValidatorProvider(IEnumerable providers) + { + if (providers == null) + { + throw new ArgumentNullException(nameof(providers)); + } + + ValidatorProviders = new List(providers); + } + + /// + /// Gets a list of instances. + /// + public IReadOnlyList ValidatorProviders { get; } + + /// + public void CreateValidators(ClientValidatorProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // Perf: Avoid allocations + for (var i = 0; i < ValidatorProviders.Count; i++) + { + ValidatorProviders[i].CreateValidators(context); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeModelValidatorProvider.cs new file mode 100644 index 0000000000..4fb2d86be3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeModelValidatorProvider.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET 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.ModelBinding.Validation +{ + /// + /// Aggregate of s that delegates to its underlying providers. + /// + public class CompositeModelValidatorProvider : IModelValidatorProvider + { + /// + /// Initializes a new instance of . + /// + /// + /// A collection of instances. + /// + public CompositeModelValidatorProvider(IList providers) + { + if (providers == null) + { + throw new ArgumentNullException(nameof(providers)); + } + + ValidatorProviders = providers; + } + + /// + /// Gets the list of instances. + /// + public IList ValidatorProviders { get; } + + /// + public void CreateValidators(ModelValidatorProviderContext context) + { + // Perf: Avoid allocations + for (var i = 0; i < ValidatorProviders.Count; i++) + { + ValidatorProviders[i].CreateValidators(context); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IObjectModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IObjectModelValidator.cs new file mode 100644 index 0000000000..1a0ca7659d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IObjectModelValidator.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. + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// Provides methods to validate an object graph. + /// + public interface IObjectModelValidator + { + /// + /// Validates the provided object. + /// + /// The associated with the current request. + /// The . May be null. + /// + /// The model prefix. Used to map the model object to entries in . + /// + /// The model object. + void Validate( + ActionContext actionContext, + ValidationStateDictionary validationState, + string prefix, + object model); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ModelValidatorProviderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ModelValidatorProviderExtensions.cs new file mode 100644 index 0000000000..3fbb512290 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ModelValidatorProviderExtensions.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// Extension methods for . + /// + public static class ModelValidatorProviderExtensions + { + /// + /// Removes all model validator providers of the specified type. + /// + /// This list of s. + /// The type to remove. + public static void RemoveType(this IList list) where TModelValidatorProvider : IModelValidatorProvider + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + RemoveType(list, typeof(TModelValidatorProvider)); + } + + /// + /// Removes all model validator providers of the specified type. + /// + /// This list of s. + /// The type to remove. + public static void RemoveType(this IList list, Type type) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + for (var i = list.Count - 1; i >= 0; i--) + { + var modelValidatorProvider = list[i]; + if (modelValidatorProvider.GetType() == type) + { + list.RemoveAt(i); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidateNeverAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidateNeverAttribute.cs new file mode 100644 index 0000000000..a0c7181f3f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidateNeverAttribute.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; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// implementation that unconditionally indicates a property should be + /// excluded from validation. When applied to a property, the validation system excludes that property. When + /// applied to a type, the validation system excludes all properties within that type. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public sealed class ValidateNeverAttribute : Attribute, IPropertyValidationFilter + { + /// + public bool ShouldValidateEntry(ValidationEntry entry, ValidationEntry parentEntry) + { + return false; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs new file mode 100644 index 0000000000..499959df27 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs @@ -0,0 +1,360 @@ +// Copyright (c) .NET 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.Runtime.CompilerServices; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// A visitor implementation that interprets to traverse + /// a model object graph and perform validation. + /// + public class ValidationVisitor + { + /// + /// Creates a new . + /// + /// The associated with the current request. + /// The . + /// The that provides a list of s. + /// The provider used for reading metadata for the model type. + /// The . + public ValidationVisitor( + ActionContext actionContext, + IModelValidatorProvider validatorProvider, + ValidatorCache validatorCache, + IModelMetadataProvider metadataProvider, + ValidationStateDictionary validationState) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + if (validatorProvider == null) + { + throw new ArgumentNullException(nameof(validatorProvider)); + } + + if (validatorCache == null) + { + throw new ArgumentNullException(nameof(validatorCache)); + } + + Context = actionContext; + ValidatorProvider = validatorProvider; + Cache = validatorCache; + + MetadataProvider = metadataProvider; + ValidationState = validationState; + + ModelState = actionContext.ModelState; + CurrentPath = new ValidationStack(); + } + + protected IModelValidatorProvider ValidatorProvider { get; } + protected IModelMetadataProvider MetadataProvider { get; } + protected ValidatorCache Cache { get; } + protected ActionContext Context { get; } + protected ModelStateDictionary ModelState { get; } + protected ValidationStateDictionary ValidationState { get; } + protected ValidationStack CurrentPath { get; } + + protected object Container { get; set; } + protected string Key { get; set; } + protected object Model { get; set; } + protected ModelMetadata Metadata { get; set; } + protected IValidationStrategy Strategy { get; set; } + + /// + /// 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. + /// + /// The associated with the model. + /// The model prefix key. + /// The model object. + /// true if the object is valid, otherwise false. + public bool Validate(ModelMetadata metadata, string key, object model) + { + return Validate(metadata, key, model, alwaysValidateAtTopLevel: false); + } + + /// + /// Validates a object. + /// + /// The associated with the model. + /// The model prefix key. + /// The model object. + /// If true, applies validation rules even if the top-level value is null. + /// true if the object is valid, otherwise false. + public virtual bool Validate(ModelMetadata metadata, string key, object model, bool alwaysValidateAtTopLevel) + { + if (model == null && key != null && !alwaysValidateAtTopLevel) + { + var entry = ModelState[key]; + if (entry != null && entry.ValidationState != ModelValidationState.Valid) + { + entry.ValidationState = ModelValidationState.Valid; + } + + return true; + } + + return Visit(metadata, key, model); + } + + /// + /// Validates a single node in a model object graph. + /// + /// true if the node is valid, otherwise false. + protected virtual bool ValidateNode() + { + var state = ModelState.GetValidationState(Key); + + // Rationale: we might see the same model state key used for two different objects. + // We want to run validation unless it's already known that this key is invalid. + if (state != ModelValidationState.Invalid) + { + var validators = Cache.GetValidators(Metadata, ValidatorProvider); + + var count = validators.Count; + if (count > 0) + { + var context = new ModelValidationContext( + Context, + Metadata, + MetadataProvider, + Container, + Model); + + var results = new List(); + for (var i = 0; i < count; i++) + { + results.AddRange(validators[i].Validate(context)); + } + + var resultsCount = results.Count; + for (var i = 0; i < resultsCount; i++) + { + var result = results[i]; + var key = ModelNames.CreatePropertyModelName(Key, result.MemberName); + + // It's OK for key to be the empty string here. This can happen when a top + // level object implements IValidatableObject. + ModelState.TryAddModelError(key, result.Message); + } + } + } + + state = ModelState.GetFieldValidationState(Key); + if (state == ModelValidationState.Invalid) + { + return false; + } + else + { + // If the field has an entry in ModelState, then record it as valid. Don't create + // extra entries if they don't exist already. + var entry = ModelState[Key]; + if (entry != null) + { + entry.ValidationState = ModelValidationState.Valid; + } + + return true; + } + } + + protected virtual bool Visit(ModelMetadata metadata, string key, object model) + { + RuntimeHelpers.EnsureSufficientExecutionStack(); + + if (model != null && !CurrentPath.Push(model)) + { + // This is a cycle, bail. + return true; + } + + var entry = GetValidationEntry(model); + key = entry?.Key ?? key ?? string.Empty; + metadata = entry?.Metadata ?? metadata; + var strategy = entry?.Strategy; + + if (ModelState.HasReachedMaxErrors) + { + SuppressValidation(key); + return false; + } + else if (entry != null && entry.SuppressValidation) + { + // Use the key on the entry, because we might not have entries in model state. + SuppressValidation(entry.Key); + CurrentPath.Pop(model); + return true; + } + + using (StateManager.Recurse(this, key ?? string.Empty, metadata, model, strategy)) + { + if (Metadata.IsEnumerableType) + { + return VisitComplexType(DefaultCollectionValidationStrategy.Instance); + } + + if (Metadata.IsComplexType) + { + return VisitComplexType(DefaultComplexObjectValidationStrategy.Instance); + } + + return VisitSimpleType(); + } + } + + // Covers everything VisitSimpleType does not i.e. both enumerations and complex types. + protected virtual bool VisitComplexType(IValidationStrategy defaultStrategy) + { + var isValid = true; + + if (Model != null && Metadata.ValidateChildren) + { + var strategy = Strategy ?? defaultStrategy; + isValid = VisitChildren(strategy); + } + else if (Model != null) + { + // Suppress validation for the entries matching this prefix. This will temporarily set + // the current node to 'skipped' but we're going to visit it right away, so subsequent + // code will set it to 'valid' or 'invalid' + SuppressValidation(Key); + } + + // Double-checking HasReachedMaxErrors just in case this model has no properties. + // If validation has failed for any children, only validate the parent if ValidateComplexTypesIfChildValidationFails is true. + if ((isValid || ValidateComplexTypesIfChildValidationFails) && !ModelState.HasReachedMaxErrors) + { + isValid &= ValidateNode(); + } + + return isValid; + } + + protected virtual bool VisitSimpleType() + { + if (ModelState.HasReachedMaxErrors) + { + SuppressValidation(Key); + return false; + } + + return ValidateNode(); + } + + protected virtual bool VisitChildren(IValidationStrategy strategy) + { + var isValid = true; + var enumerator = strategy.GetChildren(Metadata, Key, Model); + var parentEntry = new ValidationEntry(Metadata, Key, Model); + + while (enumerator.MoveNext()) + { + var entry = enumerator.Current; + var metadata = entry.Metadata; + var key = entry.Key; + if (metadata.PropertyValidationFilter?.ShouldValidateEntry(entry, parentEntry) == false) + { + SuppressValidation(key); + continue; + } + + isValid &= Visit(metadata, key, entry.Model); + } + + return isValid; + } + + protected virtual void SuppressValidation(string key) + { + if (key == null) + { + // If the key is null, that means that we shouldn't expect any entries in ModelState for + // this value, so there's nothing to do. + return; + } + + var entries = ModelState.FindKeysWithPrefix(key); + foreach (var entry in entries) + { + entry.Value.ValidationState = ModelValidationState.Skipped; + } + } + + protected virtual ValidationStateEntry GetValidationEntry(object model) + { + if (model == null || ValidationState == null) + { + return null; + } + + ValidationState.TryGetValue(model, out var entry); + return entry; + } + + protected struct StateManager : IDisposable + { + private readonly ValidationVisitor _visitor; + private readonly object _container; + private readonly string _key; + private readonly ModelMetadata _metadata; + private readonly object _model; + private readonly object _newModel; + private readonly IValidationStrategy _strategy; + + public static StateManager Recurse( + ValidationVisitor visitor, + string key, + ModelMetadata metadata, + object model, + IValidationStrategy strategy) + { + var recursifier = new StateManager(visitor, model); + + visitor.Container = visitor.Model; + visitor.Key = key; + visitor.Metadata = metadata; + visitor.Model = model; + visitor.Strategy = strategy; + + return recursifier; + } + + public StateManager(ValidationVisitor visitor, object newModel) + { + _visitor = visitor; + _newModel = newModel; + + _container = _visitor.Container; + _key = _visitor.Key; + _metadata = _visitor.Metadata; + _model = _visitor.Model; + _strategy = _visitor.Strategy; + } + + public void Dispose() + { + _visitor.Container = _container; + _visitor.Key = _key; + _visitor.Metadata = _metadata; + _visitor.Model = _model; + _visitor.Strategy = _strategy; + + _visitor.CurrentPath.Pop(_newModel); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ValueProviderFactoryExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ValueProviderFactoryExtensions.cs new file mode 100644 index 0000000000..40041c9427 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ValueProviderFactoryExtensions.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding +{ + /// + /// Extension methods for . + /// + public static class ValueProviderFactoryExtensions + { + /// + /// Removes all value provider factories of the specified type. + /// + /// The list of . + /// The type to remove. + public static void RemoveType(this IList list) where TValueProviderFactory : IValueProviderFactory + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + RemoveType(list, typeof(TValueProviderFactory)); + } + + /// + /// Removes all value provider factories of the specified type. + /// + /// The list of . + /// The type to remove. + public static void RemoveType(this IList list, Type type) + { + if (list == null) + { + throw new ArgumentNullException(nameof(list)); + } + + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + for (var i = list.Count - 1; i >= 0; i--) + { + var valueProviderFactory = list[i]; + if (valueProviderFactory.GetType() == type) + { + list.RemoveAt(i); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelMetadataTypeAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelMetadataTypeAttribute.cs new file mode 100644 index 0000000000..a794257147 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelMetadataTypeAttribute.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 System; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// This attribute specifies the metadata class to associate with a data model class. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public class ModelMetadataTypeAttribute : Attribute + { + /// + /// Initializes a new instance of the class. + /// + /// The type of metadata class that is associated with a data model class. + public ModelMetadataTypeAttribute(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + MetadataType = type; + } + + /// + /// Gets the type of metadata class that is associated with a data model class. + /// + public Type MetadataType { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs new file mode 100644 index 0000000000..72634a49fa --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.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; +using System.Collections; +using System.Collections.Generic; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Provides programmatic configuration for the MVC framework. + /// + public class MvcOptions : IEnumerable + { + private int _maxModelStateErrors = ModelStateDictionary.DefaultMaxAllowedErrors; + + // See CompatibilitySwitch.cs for guide on how to implement these. + private readonly CompatibilitySwitch _allowBindingHeaderValuesToNonStringModelTypes; + private readonly CompatibilitySwitch _allowCombiningAuthorizeFilters; + private readonly CompatibilitySwitch _allowValidatingTopLevelNodes; + private readonly CompatibilitySwitch _inputFormatterExceptionPolicy; + private readonly CompatibilitySwitch _suppressBindingUndefinedValueToEnumType; + private readonly ICompatibilitySwitch[] _switches; + + /// + /// Creates a new instance of . + /// + public MvcOptions() + { + CacheProfiles = new Dictionary(StringComparer.OrdinalIgnoreCase); + Conventions = new List(); + Filters = new FilterCollection(); + FormatterMappings = new FormatterMappings(); + InputFormatters = new FormatterCollection(); + OutputFormatters = new FormatterCollection(); + ModelBinderProviders = new List(); + ModelBindingMessageProvider = new DefaultModelBindingMessageProvider(); + ModelMetadataDetailsProviders = new List(); + ModelValidatorProviders = new List(); + ValueProviderFactories = new List(); + + _allowCombiningAuthorizeFilters = new CompatibilitySwitch(nameof(AllowCombiningAuthorizeFilters)); + _allowBindingHeaderValuesToNonStringModelTypes = new CompatibilitySwitch(nameof(AllowBindingHeaderValuesToNonStringModelTypes)); + _allowValidatingTopLevelNodes = new CompatibilitySwitch(nameof(AllowValidatingTopLevelNodes)); + _inputFormatterExceptionPolicy = new CompatibilitySwitch(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions); + _suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch(nameof(SuppressBindingUndefinedValueToEnumType)); + + _switches = new ICompatibilitySwitch[] + { + _allowCombiningAuthorizeFilters, + _allowBindingHeaderValuesToNonStringModelTypes, + _allowValidatingTopLevelNodes, + _inputFormatterExceptionPolicy, + _suppressBindingUndefinedValueToEnumType, + }; + } + + /// + /// Gets or sets the flag which decides whether body model binding (for example, on an + /// action method parameter with ) should treat empty + /// input as valid. by default. + /// + /// + /// When , actions that model bind the request body (for example, + /// using ) will register an error in the + /// if the incoming request body is empty. + /// + public bool AllowEmptyInputInBodyModelBinding { get; set; } + + /// + /// Gets or sets a value that determines if policies on instances of + /// will be combined into a single effective policy. The default value of the property is false. + /// + /// + /// + /// Authorization policies are designed such that multiple authorization policies applied to an endpoint + /// should be combined and executed a single policy. The (commonly applied + /// by ) can be applied globally, to controllers, and to actions - which + /// specifies multiple authorization policies for an action. In all ASP.NET Core releases prior to 2.1 + /// these multiple policies would not combine as intended. This compatibility switch configures whether the + /// old (unintended) behavior or the new combining behavior will be used when multiple authorization policies + /// are applied. + /// + /// + /// 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 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 AllowCombiningAuthorizeFilters + { + get => _allowCombiningAuthorizeFilters.Value; + set => _allowCombiningAuthorizeFilters.Value = value; + } + + /// + /// Gets or sets a value that determines if should bind to types other than + /// or a collection of . If set to true, + /// would bind to simple types (like , , + /// , etc.) or a collection of simple types. The default value of the + /// property is false. + /// + /// + /// + /// 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 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 AllowBindingHeaderValuesToNonStringModelTypes + { + get => _allowBindingHeaderValuesToNonStringModelTypes.Value; + set => _allowBindingHeaderValuesToNonStringModelTypes.Value = value; + } + + /// + /// Gets or sets a value that determines if model bound action parameters, controller properties, page handler + /// parameters, or page model properties are validated (in addition to validating their elements or + /// properties). If set to , and + /// ValidationAttributes on these top-level nodes are checked. Otherwise, such attributes are ignored. + /// + /// + /// 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 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 AllowValidatingTopLevelNodes + { + get => _allowValidatingTopLevelNodes.Value; + set => _allowValidatingTopLevelNodes.Value = value; + } + + /// + /// Gets a Dictionary of CacheProfile Names, which are pre-defined settings for + /// response caching. + /// + public IDictionary CacheProfiles { get; } + + /// + /// Gets a list of instances that will be applied to + /// the when discovering actions. + /// + public IList Conventions { get; } + + /// + /// Gets a collection of which are used to construct filters that + /// apply to all actions. + /// + public FilterCollection Filters { get; } + + /// + /// Used to specify mapping between the URL Format and corresponding media type. + /// + public FormatterMappings FormatterMappings { get; } + + /// + /// Gets or sets a value which determines how the model binding system interprets exceptions thrown by an . + /// The default value of the property 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 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 InputFormatterExceptionPolicy InputFormatterExceptionPolicy + { + get => _inputFormatterExceptionPolicy.Value; + set => _inputFormatterExceptionPolicy.Value = value; + } + + /// + /// Gets a list of s that are used by this application. + /// + public FormatterCollection InputFormatters { get; } + + /// + /// Gets or sets a value indicating whether the model binding system will bind undefined values to + /// enum types. The default value of the property is false. + /// + /// + /// + /// 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 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 SuppressBindingUndefinedValueToEnumType + { + get => _suppressBindingUndefinedValueToEnumType.Value; + set => _suppressBindingUndefinedValueToEnumType.Value = value; + } + + /// + /// Gets or sets the flag to buffer the request body in input formatters. Default is false. + /// + public bool SuppressInputFormatterBuffering { get; set; } = false; + + /// + /// Gets or sets the maximum number of validation errors that are allowed by this application before further + /// errors are ignored. + /// + public int MaxModelValidationErrors + { + get => _maxModelStateErrors; + set + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _maxModelStateErrors = value; + } + } + + /// + /// Gets a list of s used by this application. + /// + public IList ModelBinderProviders { get; } + + /// + /// Gets the default . Changes here are copied to the + /// property of all + /// instances unless overridden in a custom . + /// + public DefaultModelBindingMessageProvider ModelBindingMessageProvider { get; } + + /// + /// Gets a list of instances that will be used to + /// create instances. + /// + /// + /// A provider should implement one or more of the following interfaces, depending on what + /// kind of details are provided: + ///
    + ///
  • + ///
  • + ///
  • + ///
+ ///
+ public IList ModelMetadataDetailsProviders { get; } + + /// + /// Gets a list of s used by this application. + /// + public IList ModelValidatorProviders { get; } + + /// + /// Gets a list of s that are used by this application. + /// + public FormatterCollection OutputFormatters { get; } + + /// + /// Gets or sets the flag which causes content negotiation to ignore Accept header + /// when it contains the media type */*. by default. + /// + public bool RespectBrowserAcceptHeader { get; set; } + + /// + /// Gets or sets the flag which decides whether an HTTP 406 Not Acceptable response + /// will be returned if no formatter has been selected to format the response. + /// by default. + /// + public bool ReturnHttpNotAcceptable { get; set; } + + /// + /// Gets a list of used by this application. + /// + public IList ValueProviderFactories { get; } + + /// + /// Gets or sets the SSL port that is used by this application when + /// is used. If not set the port won't be specified in the secured URL e.g. https://localhost/path. + /// + public int? SslPort { get; set; } + + /// + /// Gets or sets the default value for the Permanent property of . + /// + public bool RequireHttpsPermanent { get; set; } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_switches).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NoContentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NoContentResult.cs new file mode 100644 index 0000000000..09a700398e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NoContentResult.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.Http; + +namespace Microsoft.AspNetCore.Mvc +{ + public class NoContentResult : StatusCodeResult + { + public NoContentResult() + : base(StatusCodes.Status204NoContent) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonActionAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonActionAttribute.cs new file mode 100644 index 0000000000..aa2cec5f5b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonActionAttribute.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.AspNetCore.Mvc +{ + /// + /// Indicates that a controller method is not an action method. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class NonActionAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonControllerAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonControllerAttribute.cs new file mode 100644 index 0000000000..518b7c6c50 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonControllerAttribute.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 System; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Indicates that the type and any derived types that this attribute is applied to + /// is not considered a controller by the default controller discovery mechanism. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class NonControllerAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonViewComponentAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonViewComponentAttribute.cs new file mode 100644 index 0000000000..68eda496df --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonViewComponentAttribute.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 System; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Indicates that the type and any derived types that this attribute is applied to + /// is not considered a view component by the default view component discovery mechanism. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public class NonViewComponentAttribute : Attribute + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs new file mode 100644 index 0000000000..9260784017 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.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.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that when executed will produce a Not Found (404) response. + /// + public class NotFoundObjectResult : ObjectResult + { + /// + /// Creates a new instance. + /// + /// The value to format in the entity body. + public NotFoundObjectResult(object value) + : base(value) + { + StatusCode = StatusCodes.Status404NotFound; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NotFoundResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NotFoundResult.cs new file mode 100644 index 0000000000..b4b7de1600 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NotFoundResult.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 +{ + /// + /// Represents an that when + /// executed will produce a Not Found (404) response. + /// + public class NotFoundResult : StatusCodeResult + { + /// + /// Creates a new instance. + /// + public NotFoundResult() : base(StatusCodes.Status404NotFound) + { + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs new file mode 100644 index 0000000000..1148ceffc1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.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 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 +{ + public class ObjectResult : ActionResult + { + public ObjectResult(object value) + { + Value = value; + Formatters = new FormatterCollection(); + ContentTypes = new MediaTypeCollection(); + } + + public object Value { get; set; } + + public FormatterCollection Formatters { get; set; } + + public MediaTypeCollection ContentTypes { get; set; } + + public Type DeclaredType { get; set; } + + /// + /// Gets or sets the HTTP status code. + /// + public int? StatusCode { get; set; } + + public override Task ExecuteResultAsync(ActionContext context) + { + var executor = context.HttpContext.RequestServices.GetRequiredService>(); + return executor.ExecuteAsync(context, this); + } + + /// + /// This method is called before the formatter writes to the output stream. + /// + public virtual void OnFormatting(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (StatusCode.HasValue) + { + context.HttpContext.Response.StatusCode = StatusCode.Value; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/OkObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/OkObjectResult.cs new file mode 100644 index 0000000000..2990ffc843 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/OkObjectResult.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.Http; + +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. + /// + public class OkObjectResult : ObjectResult + { + /// + /// Initializes a new instance of the class. + /// + /// The content to format into the entity body. + public OkObjectResult(object value) + : base(value) + { + StatusCode = StatusCodes.Status200OK; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/OkResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/OkResult.cs new file mode 100644 index 0000000000..49cd649c99 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/OkResult.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 Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that when executed will produce an empty + /// response. + /// + public class OkResult : StatusCodeResult + { + /// + /// Initializes a new instance of the class. + /// + public OkResult() + : base(StatusCodes.Status200OK) + { + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/PhysicalFileResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/PhysicalFileResult.cs new file mode 100644 index 0000000000..c83dc1442a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/PhysicalFileResult.cs @@ -0,0 +1,82 @@ +// Copyright (c) .NET 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.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A on execution will write a file from disk to the response + /// using mechanisms provided by the host. + /// + public class PhysicalFileResult : FileResult + { + private string _fileName; + + /// + /// Creates a new instance with + /// the provided and the provided . + /// + /// The path to the file. The path must be an absolute path. + /// The Content-Type header of the response. + public PhysicalFileResult(string fileName, string contentType) + : this(fileName, MediaTypeHeaderValue.Parse(contentType)) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + } + + /// + /// Creates a new instance with + /// the provided and the provided . + /// + /// The path to the file. The path must be an absolute path. + /// The Content-Type header of the response. + public PhysicalFileResult(string fileName, MediaTypeHeaderValue contentType) + : base(contentType?.ToString()) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + FileName = fileName; + } + + /// + /// Gets or sets the path to the file that will be sent back as the response. + /// + public string FileName + { + get => _fileName; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _fileName = value; + } + } + + /// + public override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var executor = context.HttpContext.RequestServices.GetRequiredService>(); + return executor.ExecuteAsync(context, this); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs new file mode 100644 index 0000000000..35ac215562 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.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 System.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A machine-readable format for specifying errors in HTTP API responses based on https://tools.ietf.org/html/rfc7807. + /// + public class ProblemDetails + { + /// + /// A URI reference [RFC3986] that identifies the problem type. This specification encourages that, when + /// dereferenced, it provide human-readable documentation for the problem type + /// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be + /// "about:blank". + /// + public string Type { get; set; } + + /// + /// 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). + /// + public string Title { get; set; } + + /// + /// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem. + /// + public int? Status { get; set; } + + /// + /// A human-readable explanation specific to this occurrence of the problem. + /// + 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. + /// + public string Instance { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs new file mode 100644 index 0000000000..202591a1b6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs @@ -0,0 +1,141 @@ +// Copyright (c) .NET 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.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Formatters.Internal; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A filter that specifies the expected the action will return and the supported + /// response content types. The value is used to set + /// . + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class ProducesAttribute : Attribute, IResultFilter, IOrderedFilter, IApiResponseMetadataProvider + { + /// + /// Initializes an instance of . + /// + /// The of object that is going to be written in the response. + public ProducesAttribute(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + Type = type; + ContentTypes = new MediaTypeCollection(); + } + + /// + /// Initializes an instance of with allowed content types. + /// + /// The allowed content type for a response. + /// Additional allowed content types for a response. + public ProducesAttribute(string contentType, params string[] additionalContentTypes) + { + if (contentType == null) + { + throw new ArgumentNullException(nameof(contentType)); + } + + // We want to ensure that the given provided content types are valid values, so + // we validate them using the semantics of MediaTypeHeaderValue. + MediaTypeHeaderValue.Parse(contentType); + + for (var i = 0; i < additionalContentTypes.Length; i++) + { + MediaTypeHeaderValue.Parse(additionalContentTypes[i]); + } + + ContentTypes = GetContentTypes(contentType, additionalContentTypes); + } + + /// + public Type Type { get; set; } + + /// + /// Gets or sets the supported response content types. Used to set . + /// + public MediaTypeCollection ContentTypes { get; set; } + + /// + public int StatusCode => StatusCodes.Status200OK; + + /// + public int Order { get; set; } + + /// + public virtual void OnResultExecuting(ResultExecutingContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var objectResult = context.Result as ObjectResult; + + if (objectResult != null) + { + // 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. + for (var i = 0; i < context.Filters.Count; i++) + { + var filter = context.Filters[i] as IFormatFilter; + + if (filter?.GetFormat(context) != null) + { + return; + } + } + + SetContentTypes(objectResult.ContentTypes); + } + } + + /// + public virtual void OnResultExecuted(ResultExecutedContext context) + { + } + + /// + public void SetContentTypes(MediaTypeCollection contentTypes) + { + contentTypes.Clear(); + foreach (var contentType in ContentTypes) + { + contentTypes.Add(contentType); + } + } + + private MediaTypeCollection GetContentTypes(string firstArg, string[] args) + { + var completeArgs = new List(); + completeArgs.Add(firstArg); + completeArgs.AddRange(args); + var contentTypes = new MediaTypeCollection(); + foreach (var arg in completeArgs) + { + var contentType = new MediaType(arg); + if (contentType.HasWildcard) + { + throw new InvalidOperationException( + Resources.FormatMatchAllContentTypeIsNotAllowed(arg)); + } + + contentTypes.Add(arg); + } + + return contentTypes; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesResponseTypeAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesResponseTypeAttribute.cs new file mode 100644 index 0000000000..7c0d25c6a4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesResponseTypeAttribute.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.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A filter that specifies the type of the value and status code returned by the action. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public class ProducesResponseTypeAttribute : Attribute, IApiResponseMetadataProvider + { + /// + /// Initializes an instance of . + /// + /// The HTTP response status code. + public ProducesResponseTypeAttribute(int statusCode) + : this(typeof(void), statusCode) + { + } + + /// + /// Initializes an instance of . + /// + /// The of object that is going to be written in the response. + /// The HTTP response status code. + public ProducesResponseTypeAttribute(Type type, int statusCode) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + Type = type; + StatusCode = statusCode; + } + + /// + /// Gets or sets the type of the value returned by an action. + /// + public Type Type { get; set; } + + /// + /// Gets or sets the HTTP status code of the response. + /// + public int StatusCode { get; set; } + + /// + 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/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..abaed09181 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.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. + +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Mvc.Formatters; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.TestCommon, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] +[assembly: TypeForwardedTo(typeof(InputFormatterException))] diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..d60a46fe66 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -0,0 +1,1486 @@ +// +namespace Microsoft.AspNetCore.Mvc.Core +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Mvc.Core.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// The argument '{0}' is invalid. Media types which match all types or match all subtypes are not supported. + /// + internal static string MatchAllContentTypeIsNotAllowed + { + get => GetString("MatchAllContentTypeIsNotAllowed"); + } + + /// + /// The argument '{0}' is invalid. Media types which match all types or match all subtypes are not supported. + /// + internal static string FormatMatchAllContentTypeIsNotAllowed(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("MatchAllContentTypeIsNotAllowed"), p0); + + /// + /// The content-type '{0}' added in the '{1}' property is invalid. Media types which match all types or match all subtypes are not supported. + /// + internal static string ObjectResult_MatchAllContentType + { + get => GetString("ObjectResult_MatchAllContentType"); + } + + /// + /// The content-type '{0}' added in the '{1}' property is invalid. Media types which match all types or match all subtypes are not supported. + /// + internal static string FormatObjectResult_MatchAllContentType(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ObjectResult_MatchAllContentType"), p0, p1); + + /// + /// The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task. + /// + internal static string ActionExecutor_WrappedTaskInstance + { + get => GetString("ActionExecutor_WrappedTaskInstance"); + } + + /// + /// The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task. + /// + internal static string FormatActionExecutor_WrappedTaskInstance(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("ActionExecutor_WrappedTaskInstance"), p0, p1, p2); + + /// + /// The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method. + /// + internal static string ActionExecutor_UnexpectedTaskInstance + { + get => GetString("ActionExecutor_UnexpectedTaskInstance"); + } + + /// + /// The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method. + /// + internal static string FormatActionExecutor_UnexpectedTaskInstance(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ActionExecutor_UnexpectedTaskInstance"), p0, p1); + + /// + /// An action invoker could not be created for action '{0}'. + /// + internal static string ActionInvokerFactory_CouldNotCreateInvoker + { + get => GetString("ActionInvokerFactory_CouldNotCreateInvoker"); + } + + /// + /// An action invoker could not be created for action '{0}'. + /// + internal static string FormatActionInvokerFactory_CouldNotCreateInvoker(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ActionInvokerFactory_CouldNotCreateInvoker"), p0); + + /// + /// The action descriptor must be of type '{0}'. + /// + internal static string ActionDescriptorMustBeBasedOnControllerAction + { + get => GetString("ActionDescriptorMustBeBasedOnControllerAction"); + } + + /// + /// The action descriptor must be of type '{0}'. + /// + internal static string FormatActionDescriptorMustBeBasedOnControllerAction(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ActionDescriptorMustBeBasedOnControllerAction"), p0); + + /// + /// Value cannot be null or empty. + /// + internal static string ArgumentCannotBeNullOrEmpty + { + get => GetString("ArgumentCannotBeNullOrEmpty"); + } + + /// + /// Value cannot be null or empty. + /// + internal static string FormatArgumentCannotBeNullOrEmpty() + => GetString("ArgumentCannotBeNullOrEmpty"); + + /// + /// The '{0}' property of '{1}' must not be null. + /// + internal static string PropertyOfTypeCannotBeNull + { + get => GetString("PropertyOfTypeCannotBeNull"); + } + + /// + /// The '{0}' property of '{1}' must not be null. + /// + internal static string FormatPropertyOfTypeCannotBeNull(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("PropertyOfTypeCannotBeNull"), p0, p1); + + /// + /// The '{0}' method of type '{1}' cannot return a null value. + /// + internal static string TypeMethodMustReturnNotNullValue + { + get => GetString("TypeMethodMustReturnNotNullValue"); + } + + /// + /// The '{0}' method of type '{1}' cannot return a null value. + /// + internal static string FormatTypeMethodMustReturnNotNullValue(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("TypeMethodMustReturnNotNullValue"), p0, p1); + + /// + /// The value '{0}' is invalid. + /// + internal static string ModelBinding_NullValueNotValid + { + get => GetString("ModelBinding_NullValueNotValid"); + } + + /// + /// The value '{0}' is invalid. + /// + internal static string FormatModelBinding_NullValueNotValid(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_NullValueNotValid"), p0); + + /// + /// The passed expression of expression node type '{0}' is invalid. Only simple member access expressions for model properties are supported. + /// + internal static string Invalid_IncludePropertyExpression + { + get => GetString("Invalid_IncludePropertyExpression"); + } + + /// + /// The passed expression of expression node type '{0}' is invalid. Only simple member access expressions for model properties are supported. + /// + internal static string FormatInvalid_IncludePropertyExpression(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("Invalid_IncludePropertyExpression"), p0); + + /// + /// No route matches the supplied values. + /// + internal static string NoRoutesMatched + { + get => GetString("NoRoutesMatched"); + } + + /// + /// No route matches the supplied values. + /// + internal static string FormatNoRoutesMatched() + => GetString("NoRoutesMatched"); + + /// + /// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}. + /// + internal static string AsyncActionFilter_InvalidShortCircuit + { + get => GetString("AsyncActionFilter_InvalidShortCircuit"); + } + + /// + /// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}. + /// + internal static string FormatAsyncActionFilter_InvalidShortCircuit(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("AsyncActionFilter_InvalidShortCircuit"), p0, p1, p2, p3); + + /// + /// If an {0} cancels execution by setting the {1} property of {2} to 'true', then it cannot call the next filter by invoking {3}. + /// + internal static string AsyncResultFilter_InvalidShortCircuit + { + get => GetString("AsyncResultFilter_InvalidShortCircuit"); + } + + /// + /// If an {0} cancels execution by setting the {1} property of {2} to 'true', then it cannot call the next filter by invoking {3}. + /// + internal static string FormatAsyncResultFilter_InvalidShortCircuit(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("AsyncResultFilter_InvalidShortCircuit"), p0, p1, p2, p3); + + /// + /// The type provided to '{0}' must implement '{1}'. + /// + internal static string FilterFactoryAttribute_TypeMustImplementIFilter + { + get => GetString("FilterFactoryAttribute_TypeMustImplementIFilter"); + } + + /// + /// The type provided to '{0}' must implement '{1}'. + /// + internal static string FormatFilterFactoryAttribute_TypeMustImplementIFilter(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("FilterFactoryAttribute_TypeMustImplementIFilter"), p0, p1); + + /// + /// Cannot return null from an action method with a return type of '{0}'. + /// + internal static string ActionResult_ActionReturnValueCannotBeNull + { + get => GetString("ActionResult_ActionReturnValueCannotBeNull"); + } + + /// + /// Cannot return null from an action method with a return type of '{0}'. + /// + internal static string FormatActionResult_ActionReturnValueCannotBeNull(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ActionResult_ActionReturnValueCannotBeNull"), p0); + + /// + /// The type '{0}' must derive from '{1}'. + /// + internal static string TypeMustDeriveFromType + { + get => GetString("TypeMustDeriveFromType"); + } + + /// + /// The type '{0}' must derive from '{1}'. + /// + internal static string FormatTypeMustDeriveFromType(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("TypeMustDeriveFromType"), p0, p1); + + /// + /// No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content. + /// + internal static string InputFormatterNoEncoding + { + get => GetString("InputFormatterNoEncoding"); + } + + /// + /// No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content. + /// + internal static string FormatInputFormatterNoEncoding(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("InputFormatterNoEncoding"), p0); + + /// + /// Unsupported content type '{0}'. + /// + internal static string UnsupportedContentType + { + get => GetString("UnsupportedContentType"); + } + + /// + /// Unsupported content type '{0}'. + /// + internal static string FormatUnsupportedContentType(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedContentType"), p0); + + /// + /// No supported media type registered for output formatter '{0}'. There must be at least one supported media type registered in order for the output formatter to write content. + /// + internal static string OutputFormatterNoMediaType + { + get => GetString("OutputFormatterNoMediaType"); + } + + /// + /// No supported media type registered for output formatter '{0}'. There must be at least one supported media type registered in order for the output formatter to write content. + /// + internal static string FormatOutputFormatterNoMediaType(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("OutputFormatterNoMediaType"), p0); + + /// + /// The following errors occurred with attribute routing information:{0}{0}{1} + /// + internal static string AttributeRoute_AggregateErrorMessage + { + get => GetString("AttributeRoute_AggregateErrorMessage"); + } + + /// + /// The following errors occurred with attribute routing information:{0}{0}{1} + /// + internal static string FormatAttributeRoute_AggregateErrorMessage(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("AttributeRoute_AggregateErrorMessage"), p0, p1); + + /// + /// The attribute route '{0}' cannot contain a parameter named '{{{1}}}'. Use '[{1}]' in the route template to insert the value '{2}'. + /// + internal static string AttributeRoute_CannotContainParameter + { + get => GetString("AttributeRoute_CannotContainParameter"); + } + + /// + /// The attribute route '{0}' cannot contain a parameter named '{{{1}}}'. Use '[{1}]' in the route template to insert the value '{2}'. + /// + internal static string FormatAttributeRoute_CannotContainParameter(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("AttributeRoute_CannotContainParameter"), p0, p1, p2); + + /// + /// For action: '{0}'{1}Error: {2} + /// + internal static string AttributeRoute_IndividualErrorMessage + { + get => GetString("AttributeRoute_IndividualErrorMessage"); + } + + /// + /// For action: '{0}'{1}Error: {2} + /// + internal static string FormatAttributeRoute_IndividualErrorMessage(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("AttributeRoute_IndividualErrorMessage"), p0, p1, p2); + + /// + /// An empty replacement token ('[]') is not allowed. + /// + internal static string AttributeRoute_TokenReplacement_EmptyTokenNotAllowed + { + get => GetString("AttributeRoute_TokenReplacement_EmptyTokenNotAllowed"); + } + + /// + /// An empty replacement token ('[]') is not allowed. + /// + internal static string FormatAttributeRoute_TokenReplacement_EmptyTokenNotAllowed() + => GetString("AttributeRoute_TokenReplacement_EmptyTokenNotAllowed"); + + /// + /// Token delimiters ('[', ']') are imbalanced. + /// + internal static string AttributeRoute_TokenReplacement_ImbalancedSquareBrackets + { + get => GetString("AttributeRoute_TokenReplacement_ImbalancedSquareBrackets"); + } + + /// + /// Token delimiters ('[', ']') are imbalanced. + /// + internal static string FormatAttributeRoute_TokenReplacement_ImbalancedSquareBrackets() + => GetString("AttributeRoute_TokenReplacement_ImbalancedSquareBrackets"); + + /// + /// The route template '{0}' has invalid syntax. {1} + /// + internal static string AttributeRoute_TokenReplacement_InvalidSyntax + { + get => GetString("AttributeRoute_TokenReplacement_InvalidSyntax"); + } + + /// + /// The route template '{0}' has invalid syntax. {1} + /// + internal static string FormatAttributeRoute_TokenReplacement_InvalidSyntax(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("AttributeRoute_TokenReplacement_InvalidSyntax"), p0, p1); + + /// + /// While processing template '{0}', a replacement value for the token '{1}' could not be found. Available tokens: '{2}'. To use a '[' or ']' as a literal string in a route or within a constraint, use '[[' or ']]' instead. + /// + internal static string AttributeRoute_TokenReplacement_ReplacementValueNotFound + { + get => GetString("AttributeRoute_TokenReplacement_ReplacementValueNotFound"); + } + + /// + /// While processing template '{0}', a replacement value for the token '{1}' could not be found. Available tokens: '{2}'. To use a '[' or ']' as a literal string in a route or within a constraint, use '[[' or ']]' instead. + /// + internal static string FormatAttributeRoute_TokenReplacement_ReplacementValueNotFound(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("AttributeRoute_TokenReplacement_ReplacementValueNotFound"), p0, p1, p2); + + /// + /// A replacement token is not closed. + /// + internal static string AttributeRoute_TokenReplacement_UnclosedToken + { + get => GetString("AttributeRoute_TokenReplacement_UnclosedToken"); + } + + /// + /// A replacement token is not closed. + /// + internal static string FormatAttributeRoute_TokenReplacement_UnclosedToken() + => GetString("AttributeRoute_TokenReplacement_UnclosedToken"); + + /// + /// An unescaped '[' token is not allowed inside of a replacement token. Use '[[' to escape. + /// + internal static string AttributeRoute_TokenReplacement_UnescapedBraceInToken + { + get => GetString("AttributeRoute_TokenReplacement_UnescapedBraceInToken"); + } + + /// + /// An unescaped '[' token is not allowed inside of a replacement token. Use '[[' to escape. + /// + internal static string FormatAttributeRoute_TokenReplacement_UnescapedBraceInToken() + => GetString("AttributeRoute_TokenReplacement_UnescapedBraceInToken"); + + /// + /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. + /// + internal static string UnableToFindServices + { + get => GetString("UnableToFindServices"); + } + + /// + /// Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. + /// + internal static string FormatUnableToFindServices(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("UnableToFindServices"), p0, p1, p2); + + /// + /// Action: '{0}' - Template: '{1}' + /// + internal static string AttributeRoute_DuplicateNames_Item + { + get => GetString("AttributeRoute_DuplicateNames_Item"); + } + + /// + /// Action: '{0}' - Template: '{1}' + /// + internal static string FormatAttributeRoute_DuplicateNames_Item(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("AttributeRoute_DuplicateNames_Item"), p0, p1); + + /// + /// Attribute routes with the same name '{0}' must have the same template:{1}{2} + /// + internal static string AttributeRoute_DuplicateNames + { + get => GetString("AttributeRoute_DuplicateNames"); + } + + /// + /// Attribute routes with the same name '{0}' must have the same template:{1}{2} + /// + internal static string FormatAttributeRoute_DuplicateNames(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("AttributeRoute_DuplicateNames"), p0, p1, p2); + + /// + /// Error {0}:{1}{2} + /// + internal static string AttributeRoute_AggregateErrorMessage_ErrorNumber + { + get => GetString("AttributeRoute_AggregateErrorMessage_ErrorNumber"); + } + + /// + /// Error {0}:{1}{2} + /// + internal static string FormatAttributeRoute_AggregateErrorMessage_ErrorNumber(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("AttributeRoute_AggregateErrorMessage_ErrorNumber"), p0, p1, p2); + + /// + /// A method '{0}' must not define attribute routed actions and non attribute routed actions at the same time:{1}{2}{1}{1}Use 'AcceptVerbsAttribute' to create a single route that allows multiple HTTP verbs and defines a route, or set a route template in all attributes that constrain HTTP verbs. + /// + internal static string AttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod + { + get => GetString("AttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod"); + } + + /// + /// A method '{0}' must not define attribute routed actions and non attribute routed actions at the same time:{1}{2}{1}{1}Use 'AcceptVerbsAttribute' to create a single route that allows multiple HTTP verbs and defines a route, or set a route template in all attributes that constrain HTTP verbs. + /// + internal static string FormatAttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("AttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod"), p0, p1, p2); + + /// + /// Action: '{0}' - Route Template: '{1}' - HTTP Verbs: '{2}' + /// + internal static string AttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod_Item + { + get => GetString("AttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod_Item"); + } + + /// + /// Action: '{0}' - Route Template: '{1}' - HTTP Verbs: '{2}' + /// + internal static string FormatAttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod_Item(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("AttributeRoute_MixedAttributeAndConventionallyRoutedActions_ForMethod_Item"), p0, p1, p2); + + /// + /// (none) + /// + internal static string AttributeRoute_NullTemplateRepresentation + { + get => GetString("AttributeRoute_NullTemplateRepresentation"); + } + + /// + /// (none) + /// + internal static string FormatAttributeRoute_NullTemplateRepresentation() + => GetString("AttributeRoute_NullTemplateRepresentation"); + + /// + /// Multiple actions matched. The following actions matched route data and had all constraints satisfied:{0}{0}{1} + /// + internal static string DefaultActionSelector_AmbiguousActions + { + get => GetString("DefaultActionSelector_AmbiguousActions"); + } + + /// + /// Multiple actions matched. The following actions matched route data and had all constraints satisfied:{0}{0}{1} + /// + internal static string FormatDefaultActionSelector_AmbiguousActions(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("DefaultActionSelector_AmbiguousActions"), p0, p1); + + /// + /// Could not find file: {0} + /// + internal static string FileResult_InvalidPath + { + get => GetString("FileResult_InvalidPath"); + } + + /// + /// Could not find file: {0} + /// + internal static string FormatFileResult_InvalidPath(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("FileResult_InvalidPath"), p0); + + /// + /// The input was not valid. + /// + internal static string SerializableError_DefaultError + { + get => GetString("SerializableError_DefaultError"); + } + + /// + /// The input was not valid. + /// + internal static string FormatSerializableError_DefaultError() + => GetString("SerializableError_DefaultError"); + + /// + /// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}. + /// + internal static string AsyncResourceFilter_InvalidShortCircuit + { + get => GetString("AsyncResourceFilter_InvalidShortCircuit"); + } + + /// + /// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}. + /// + internal static string FormatAsyncResourceFilter_InvalidShortCircuit(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("AsyncResourceFilter_InvalidShortCircuit"), p0, p1, p2, p3); + + /// + /// If the '{0}' property is not set to true, '{1}' property must be specified. + /// + internal static string ResponseCache_SpecifyDuration + { + get => GetString("ResponseCache_SpecifyDuration"); + } + + /// + /// If the '{0}' property is not set to true, '{1}' property must be specified. + /// + internal static string FormatResponseCache_SpecifyDuration(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ResponseCache_SpecifyDuration"), p0, p1); + + /// + /// The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer. + /// + internal static string ApiExplorer_UnsupportedAction + { + get => GetString("ApiExplorer_UnsupportedAction"); + } + + /// + /// The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer. + /// + internal static string FormatApiExplorer_UnsupportedAction(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ApiExplorer_UnsupportedAction"), p0); + + /// + /// The media type "{0}" is not valid. MediaTypes containing wildcards (*) are not allowed in formatter mappings. + /// + internal static string FormatterMappings_NotValidMediaType + { + get => GetString("FormatterMappings_NotValidMediaType"); + } + + /// + /// The media type "{0}" is not valid. MediaTypes containing wildcards (*) are not allowed in formatter mappings. + /// + internal static string FormatFormatterMappings_NotValidMediaType(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("FormatterMappings_NotValidMediaType"), p0); + + /// + /// The format provided is invalid '{0}'. A format must be a non-empty file-extension, optionally prefixed with a '.' character. + /// + internal static string Format_NotValid + { + get => GetString("Format_NotValid"); + } + + /// + /// The format provided is invalid '{0}'. A format must be a non-empty file-extension, optionally prefixed with a '.' character. + /// + internal static string FormatFormat_NotValid(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("Format_NotValid"), p0); + + /// + /// The '{0}' cache profile is not defined. + /// + internal static string CacheProfileNotFound + { + get => GetString("CacheProfileNotFound"); + } + + /// + /// The '{0}' cache profile is not defined. + /// + internal static string FormatCacheProfileNotFound(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("CacheProfileNotFound"), p0); + + /// + /// The model's runtime type '{0}' is not assignable to the type '{1}'. + /// + internal static string ModelType_WrongType + { + get => GetString("ModelType_WrongType"); + } + + /// + /// The model's runtime type '{0}' is not assignable to the type '{1}'. + /// + internal static string FormatModelType_WrongType(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ModelType_WrongType"), p0, p1); + + /// + /// The type '{0}' cannot be activated by '{1}' because it is either a value type, an interface, an abstract class or an open generic type. + /// + internal static string ValueInterfaceAbstractOrOpenGenericTypesCannotBeActivated + { + get => GetString("ValueInterfaceAbstractOrOpenGenericTypesCannotBeActivated"); + } + + /// + /// The type '{0}' cannot be activated by '{1}' because it is either a value type, an interface, an abstract class or an open generic type. + /// + internal static string FormatValueInterfaceAbstractOrOpenGenericTypesCannotBeActivated(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ValueInterfaceAbstractOrOpenGenericTypesCannotBeActivated"), p0, p1); + + /// + /// The type '{0}' must implement '{1}' to be used as a model binder. + /// + internal static string BinderType_MustBeIModelBinder + { + get => GetString("BinderType_MustBeIModelBinder"); + } + + /// + /// The type '{0}' must implement '{1}' to be used as a model binder. + /// + internal static string FormatBinderType_MustBeIModelBinder(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("BinderType_MustBeIModelBinder"), p0, p1); + + /// + /// The provided binding source '{0}' is a composite. '{1}' requires that the source must represent a single type of input. + /// + internal static string BindingSource_CannotBeComposite + { + get => GetString("BindingSource_CannotBeComposite"); + } + + /// + /// The provided binding source '{0}' is a composite. '{1}' requires that the source must represent a single type of input. + /// + internal static string FormatBindingSource_CannotBeComposite(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_CannotBeComposite"), p0, p1); + + /// + /// The provided binding source '{0}' is a greedy data source. '{1}' does not support greedy data sources. + /// + internal static string BindingSource_CannotBeGreedy + { + get => GetString("BindingSource_CannotBeGreedy"); + } + + /// + /// The provided binding source '{0}' is a greedy data source. '{1}' does not support greedy data sources. + /// + internal static string FormatBindingSource_CannotBeGreedy(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("BindingSource_CannotBeGreedy"), p0, p1); + + /// + /// The property {0}.{1} could not be found. + /// + internal static string Common_PropertyNotFound + { + get => GetString("Common_PropertyNotFound"); + } + + /// + /// The property {0}.{1} could not be found. + /// + internal static string FormatCommon_PropertyNotFound(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("Common_PropertyNotFound"), p0, p1); + + /// + /// The key '{0}' is invalid JQuery syntax because it is missing a closing bracket. + /// + internal static string JQueryFormValueProviderFactory_MissingClosingBracket + { + get => GetString("JQueryFormValueProviderFactory_MissingClosingBracket"); + } + + /// + /// The key '{0}' is invalid JQuery syntax because it is missing a closing bracket. + /// + internal static string FormatJQueryFormValueProviderFactory_MissingClosingBracket(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("JQueryFormValueProviderFactory_MissingClosingBracket"), p0); + + /// + /// A value is required. + /// + internal static string KeyValuePair_BothKeyAndValueMustBePresent + { + get => GetString("KeyValuePair_BothKeyAndValueMustBePresent"); + } + + /// + /// A value is required. + /// + internal static string FormatKeyValuePair_BothKeyAndValueMustBePresent() + => GetString("KeyValuePair_BothKeyAndValueMustBePresent"); + + /// + /// The binding context has a null Model, but this binder requires a non-null model of type '{0}'. + /// + internal static string ModelBinderUtil_ModelCannotBeNull + { + get => GetString("ModelBinderUtil_ModelCannotBeNull"); + } + + /// + /// The binding context has a null Model, but this binder requires a non-null model of type '{0}'. + /// + internal static string FormatModelBinderUtil_ModelCannotBeNull(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ModelBinderUtil_ModelCannotBeNull"), p0); + + /// + /// The binding context has a Model of type '{0}', but this binder can only operate on models of type '{1}'. + /// + internal static string ModelBinderUtil_ModelInstanceIsWrong + { + get => GetString("ModelBinderUtil_ModelInstanceIsWrong"); + } + + /// + /// The binding context has a Model of type '{0}', but this binder can only operate on models of type '{1}'. + /// + internal static string FormatModelBinderUtil_ModelInstanceIsWrong(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ModelBinderUtil_ModelInstanceIsWrong"), p0, p1); + + /// + /// The binding context cannot have a null ModelMetadata. + /// + internal static string ModelBinderUtil_ModelMetadataCannotBeNull + { + get => GetString("ModelBinderUtil_ModelMetadataCannotBeNull"); + } + + /// + /// The binding context cannot have a null ModelMetadata. + /// + internal static string FormatModelBinderUtil_ModelMetadataCannotBeNull() + => GetString("ModelBinderUtil_ModelMetadataCannotBeNull"); + + /// + /// A value for the '{0}' property was not provided. + /// + internal static string ModelBinding_MissingBindRequiredMember + { + get => GetString("ModelBinding_MissingBindRequiredMember"); + } + + /// + /// A value for the '{0}' property was not provided. + /// + internal static string FormatModelBinding_MissingBindRequiredMember(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_MissingBindRequiredMember"), p0); + + /// + /// A non-empty request body is required. + /// + internal static string ModelBinding_MissingRequestBodyRequiredMember + { + get => GetString("ModelBinding_MissingRequestBodyRequiredMember"); + } + + /// + /// A non-empty request body is required. + /// + internal static string FormatModelBinding_MissingRequestBodyRequiredMember() + => GetString("ModelBinding_MissingRequestBodyRequiredMember"); + + /// + /// The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types. + /// + internal static string ValueProviderResult_NoConverterExists + { + get => GetString("ValueProviderResult_NoConverterExists"); + } + + /// + /// The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types. + /// + internal static string FormatValueProviderResult_NoConverterExists(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ValueProviderResult_NoConverterExists"), p0, p1); + + /// + /// Path '{0}' was not rooted. + /// + internal static string FileResult_PathNotRooted + { + get => GetString("FileResult_PathNotRooted"); + } + + /// + /// Path '{0}' was not rooted. + /// + internal static string FormatFileResult_PathNotRooted(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("FileResult_PathNotRooted"), p0); + + /// + /// The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local. + /// + internal static string UrlNotLocal + { + get => GetString("UrlNotLocal"); + } + + /// + /// The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local. + /// + internal static string FormatUrlNotLocal() + => GetString("UrlNotLocal"); + + /// + /// The argument '{0}' is invalid. Empty or null formats are not supported. + /// + internal static string FormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat + { + get => GetString("FormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat"); + } + + /// + /// The argument '{0}' is invalid. Empty or null formats are not supported. + /// + internal static string FormatFormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("FormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat"), p0); + + /// + /// "Invalid values '{0}'." + /// + internal static string AcceptHeaderParser_ParseAcceptHeader_InvalidValues + { + get => GetString("AcceptHeaderParser_ParseAcceptHeader_InvalidValues"); + } + + /// + /// "Invalid values '{0}'." + /// + internal static string FormatAcceptHeaderParser_ParseAcceptHeader_InvalidValues(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("AcceptHeaderParser_ParseAcceptHeader_InvalidValues"), p0); + + /// + /// The value '{0}' is not valid for {1}. + /// + internal static string ModelState_AttemptedValueIsInvalid + { + get => GetString("ModelState_AttemptedValueIsInvalid"); + } + + /// + /// The value '{0}' is not valid for {1}. + /// + internal static string FormatModelState_AttemptedValueIsInvalid(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ModelState_AttemptedValueIsInvalid"), p0, p1); + + /// + /// The value '{0}' is not valid. + /// + internal static string ModelState_NonPropertyAttemptedValueIsInvalid + { + get => GetString("ModelState_NonPropertyAttemptedValueIsInvalid"); + } + + /// + /// The value '{0}' is not valid. + /// + internal static string FormatModelState_NonPropertyAttemptedValueIsInvalid(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ModelState_NonPropertyAttemptedValueIsInvalid"), p0); + + /// + /// The supplied value is invalid for {0}. + /// + internal static string ModelState_UnknownValueIsInvalid + { + get => GetString("ModelState_UnknownValueIsInvalid"); + } + + /// + /// The supplied value is invalid for {0}. + /// + internal static string FormatModelState_UnknownValueIsInvalid(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ModelState_UnknownValueIsInvalid"), p0); + + /// + /// The supplied value is invalid. + /// + internal static string ModelState_NonPropertyUnknownValueIsInvalid + { + get => GetString("ModelState_NonPropertyUnknownValueIsInvalid"); + } + + /// + /// The supplied value is invalid. + /// + internal static string FormatModelState_NonPropertyUnknownValueIsInvalid() + => GetString("ModelState_NonPropertyUnknownValueIsInvalid"); + + /// + /// The value '{0}' is invalid. + /// + internal static string HtmlGeneration_ValueIsInvalid + { + get => GetString("HtmlGeneration_ValueIsInvalid"); + } + + /// + /// The value '{0}' is invalid. + /// + internal static string FormatHtmlGeneration_ValueIsInvalid(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("HtmlGeneration_ValueIsInvalid"), p0); + + /// + /// The field {0} must be a number. + /// + internal static string HtmlGeneration_ValueMustBeNumber + { + get => GetString("HtmlGeneration_ValueMustBeNumber"); + } + + /// + /// The field {0} must be a number. + /// + internal static string FormatHtmlGeneration_ValueMustBeNumber(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("HtmlGeneration_ValueMustBeNumber"), p0); + + /// + /// The field must be a number. + /// + internal static string HtmlGeneration_NonPropertyValueMustBeNumber + { + get => GetString("HtmlGeneration_NonPropertyValueMustBeNumber"); + } + + /// + /// The field must be a number. + /// + internal static string FormatHtmlGeneration_NonPropertyValueMustBeNumber() + => GetString("HtmlGeneration_NonPropertyValueMustBeNumber"); + + /// + /// The list of '{0}' must not be empty. Add at least one supported encoding. + /// + internal static string TextInputFormatter_SupportedEncodingsMustNotBeEmpty + { + get => GetString("TextInputFormatter_SupportedEncodingsMustNotBeEmpty"); + } + + /// + /// The list of '{0}' must not be empty. Add at least one supported encoding. + /// + internal static string FormatTextInputFormatter_SupportedEncodingsMustNotBeEmpty(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("TextInputFormatter_SupportedEncodingsMustNotBeEmpty"), p0); + + /// + /// The list of '{0}' must not be empty. Add at least one supported encoding. + /// + internal static string TextOutputFormatter_SupportedEncodingsMustNotBeEmpty + { + get => GetString("TextOutputFormatter_SupportedEncodingsMustNotBeEmpty"); + } + + /// + /// The list of '{0}' must not be empty. Add at least one supported encoding. + /// + internal static string FormatTextOutputFormatter_SupportedEncodingsMustNotBeEmpty(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("TextOutputFormatter_SupportedEncodingsMustNotBeEmpty"), p0); + + /// + /// '{0}' is not supported by '{1}'. Use '{2}' instead. + /// + internal static string TextOutputFormatter_WriteResponseBodyAsyncNotSupported + { + get => GetString("TextOutputFormatter_WriteResponseBodyAsyncNotSupported"); + } + + /// + /// '{0}' is not supported by '{1}'. Use '{2}' instead. + /// + internal static string FormatTextOutputFormatter_WriteResponseBodyAsyncNotSupported(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("TextOutputFormatter_WriteResponseBodyAsyncNotSupported"), p0, p1, p2); + + /// + /// No media types found in '{0}.{1}'. Add at least one media type to the list of supported media types. + /// + internal static string Formatter_NoMediaTypes + { + get => GetString("Formatter_NoMediaTypes"); + } + + /// + /// No media types found in '{0}.{1}'. Add at least one media type to the list of supported media types. + /// + internal static string FormatFormatter_NoMediaTypes(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("Formatter_NoMediaTypes"), p0, p1); + + /// + /// Could not create a model binder for model object of type '{0}'. + /// + internal static string CouldNotCreateIModelBinder + { + get => GetString("CouldNotCreateIModelBinder"); + } + + /// + /// Could not create a model binder for model object of type '{0}'. + /// + internal static string FormatCouldNotCreateIModelBinder(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("CouldNotCreateIModelBinder"), p0); + + /// + /// '{0}.{1}' must not be empty. At least one '{2}' is required to bind from the body. + /// + internal static string InputFormattersAreRequired + { + get => GetString("InputFormattersAreRequired"); + } + + /// + /// '{0}.{1}' must not be empty. At least one '{2}' is required to bind from the body. + /// + internal static string FormatInputFormattersAreRequired(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("InputFormattersAreRequired"), p0, p1, p2); + + /// + /// '{0}.{1}' must not be empty. At least one '{2}' is required to model bind. + /// + internal static string ModelBinderProvidersAreRequired + { + get => GetString("ModelBinderProvidersAreRequired"); + } + + /// + /// '{0}.{1}' must not be empty. At least one '{2}' is required to model bind. + /// + internal static string FormatModelBinderProvidersAreRequired(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("ModelBinderProvidersAreRequired"), p0, p1, p2); + + /// + /// '{0}.{1}' must not be empty. At least one '{2}' is required to format a response. + /// + internal static string OutputFormattersAreRequired + { + get => GetString("OutputFormattersAreRequired"); + } + + /// + /// '{0}.{1}' must not be empty. At least one '{2}' is required to format a response. + /// + internal static string FormatOutputFormattersAreRequired(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("OutputFormattersAreRequired"), p0, p1, p2); + + /// + /// Multiple overloads of method '{0}' are not supported. + /// + internal static string MiddewareFilter_ConfigureMethodOverload + { + get => GetString("MiddewareFilter_ConfigureMethodOverload"); + } + + /// + /// Multiple overloads of method '{0}' are not supported. + /// + internal static string FormatMiddewareFilter_ConfigureMethodOverload(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("MiddewareFilter_ConfigureMethodOverload"), p0); + + /// + /// A public method named '{0}' could not be found in the '{1}' type. + /// + internal static string MiddewareFilter_NoConfigureMethod + { + get => GetString("MiddewareFilter_NoConfigureMethod"); + } + + /// + /// A public method named '{0}' could not be found in the '{1}' type. + /// + internal static string FormatMiddewareFilter_NoConfigureMethod(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("MiddewareFilter_NoConfigureMethod"), p0, p1); + + /// + /// Could not find '{0}' in the feature list. + /// + internal static string MiddlewareFilterBuilder_NoMiddlewareFeature + { + get => GetString("MiddlewareFilterBuilder_NoMiddlewareFeature"); + } + + /// + /// Could not find '{0}' in the feature list. + /// + internal static string FormatMiddlewareFilterBuilder_NoMiddlewareFeature(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilterBuilder_NoMiddlewareFeature"), p0); + + /// + /// The '{0}' property cannot be null. + /// + internal static string MiddlewareFilterBuilder_NullApplicationBuilder + { + get => GetString("MiddlewareFilterBuilder_NullApplicationBuilder"); + } + + /// + /// The '{0}' property cannot be null. + /// + internal static string FormatMiddlewareFilterBuilder_NullApplicationBuilder(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilterBuilder_NullApplicationBuilder"), p0); + + /// + /// The '{0}' method in the type '{1}' must have a return type of '{2}'. + /// + internal static string MiddlewareFilter_InvalidConfigureReturnType + { + get => GetString("MiddlewareFilter_InvalidConfigureReturnType"); + } + + /// + /// The '{0}' method in the type '{1}' must have a return type of '{2}'. + /// + internal static string FormatMiddlewareFilter_InvalidConfigureReturnType(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilter_InvalidConfigureReturnType"), p0, p1, p2); + + /// + /// Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'. + /// + internal static string MiddlewareFilter_ServiceResolutionFail + { + get => GetString("MiddlewareFilter_ServiceResolutionFail"); + } + + /// + /// Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'. + /// + internal static string FormatMiddlewareFilter_ServiceResolutionFail(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilter_ServiceResolutionFail"), p0, p1, p2, p3); + + /// + /// An {0} cannot be created without a valid instance of {1}. + /// + internal static string AuthorizeFilter_AuthorizationPolicyCannotBeCreated + { + get => GetString("AuthorizeFilter_AuthorizationPolicyCannotBeCreated"); + } + + /// + /// An {0} cannot be created without a valid instance of {1}. + /// + internal static string FormatAuthorizeFilter_AuthorizationPolicyCannotBeCreated(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("AuthorizeFilter_AuthorizationPolicyCannotBeCreated"), p0, p1); + + /// + /// The '{0}' cannot bind to a model of type '{1}'. Change the model type to '{2}' instead. + /// + internal static string FormCollectionModelBinder_CannotBindToFormCollection + { + get => GetString("FormCollectionModelBinder_CannotBindToFormCollection"); + } + + /// + /// The '{0}' cannot bind to a model of type '{1}'. Change the model type to '{2}' instead. + /// + internal static string FormatFormCollectionModelBinder_CannotBindToFormCollection(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("FormCollectionModelBinder_CannotBindToFormCollection"), p0, p1, p2); + + /// + /// '{0}' requires the response cache middleware. + /// + internal static string VaryByQueryKeys_Requires_ResponseCachingMiddleware + { + get => GetString("VaryByQueryKeys_Requires_ResponseCachingMiddleware"); + } + + /// + /// '{0}' requires the response cache middleware. + /// + internal static string FormatVaryByQueryKeys_Requires_ResponseCachingMiddleware(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("VaryByQueryKeys_Requires_ResponseCachingMiddleware"), p0); + + /// + /// A duplicate entry for library reference {0} was found. Please check that all package references in all projects use the same casing for the same package references. + /// + internal static string CandidateResolver_DifferentCasedReference + { + get => GetString("CandidateResolver_DifferentCasedReference"); + } + + /// + /// A duplicate entry for library reference {0} was found. Please check that all package references in all projects use the same casing for the same package references. + /// + internal static string FormatCandidateResolver_DifferentCasedReference(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("CandidateResolver_DifferentCasedReference"), p0); + + /// + /// Unable to create an instance of type '{0}'. The type specified in {1} must not be abstract and must have a parameterless constructor. + /// + internal static string MiddlewareFilterConfigurationProvider_CreateConfigureDelegate_CannotCreateType + { + get => GetString("MiddlewareFilterConfigurationProvider_CreateConfigureDelegate_CannotCreateType"); + } + + /// + /// Unable to create an instance of type '{0}'. The type specified in {1} must not be abstract and must have a parameterless constructor. + /// + internal static string FormatMiddlewareFilterConfigurationProvider_CreateConfigureDelegate_CannotCreateType(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("MiddlewareFilterConfigurationProvider_CreateConfigureDelegate_CannotCreateType"), p0, p1); + + /// + /// '{0}' and '{1}' are out of bounds for the string. + /// + internal static string Argument_InvalidOffsetLength + { + get => GetString("Argument_InvalidOffsetLength"); + } + + /// + /// '{0}' and '{1}' are out of bounds for the string. + /// + internal static string FormatArgument_InvalidOffsetLength(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("Argument_InvalidOffsetLength"), p0, p1); + + /// + /// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. + /// + internal static string ComplexTypeModelBinder_NoParameterlessConstructor_ForType + { + get => GetString("ComplexTypeModelBinder_NoParameterlessConstructor_ForType"); + } + + /// + /// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. + /// + internal static string FormatComplexTypeModelBinder_NoParameterlessConstructor_ForType(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ComplexTypeModelBinder_NoParameterlessConstructor_ForType"), p0); + + /// + /// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, set the '{1}' property to a non-null value in the '{2}' constructor. + /// + internal static string ComplexTypeModelBinder_NoParameterlessConstructor_ForProperty + { + get => GetString("ComplexTypeModelBinder_NoParameterlessConstructor_ForProperty"); + } + + /// + /// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, set the '{1}' property to a non-null value in the '{2}' constructor. + /// + internal static string FormatComplexTypeModelBinder_NoParameterlessConstructor_ForProperty(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("ComplexTypeModelBinder_NoParameterlessConstructor_ForProperty"), p0, p1, p2); + + /// + /// No page named '{0}' matches the supplied values. + /// + internal static string NoRoutesMatchedForPage + { + get => GetString("NoRoutesMatchedForPage"); + } + + /// + /// No page named '{0}' matches the supplied values. + /// + internal static string FormatNoRoutesMatchedForPage(object p0) + => 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. + /// + internal static string UrlHelper_RelativePagePathIsNotSupported + { + get => GetString("UrlHelper_RelativePagePathIsNotSupported"); + } + + /// + /// 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. + /// + internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0); + + /// + /// One or more validation errors occurred. + /// + internal static string ValidationProblemDescription_Title + { + get => GetString("ValidationProblemDescription_Title"); + } + + /// + /// One or more validation errors occurred. + /// + internal static string FormatValidationProblemDescription_Title() + => GetString("ValidationProblemDescription_Title"); + + /// + /// Action '{0}' does not have an attribute route. Action methods on controllers annotated with {1} must be attribute routed. + /// + internal static string ApiController_AttributeRouteRequired + { + get => GetString("ApiController_AttributeRouteRequired"); + } + + /// + /// Action '{0}' does not have an attribute route. Action methods on controllers annotated with {1} must be attribute routed. + /// + internal static string FormatApiController_AttributeRouteRequired(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ApiController_AttributeRouteRequired"), p0, p1); + + /// + /// No file provider has been configured to process the supplied file. + /// + internal static string VirtualFileResultExecutor_NoFileProviderConfigured + { + get => GetString("VirtualFileResultExecutor_NoFileProviderConfigured"); + } + + /// + /// No file provider has been configured to process the supplied file. + /// + internal static string FormatVirtualFileResultExecutor_NoFileProviderConfigured() + => GetString("VirtualFileResultExecutor_NoFileProviderConfigured"); + + /// + /// Type {0} specified by {1} is invalid. Type specified by {1} must derive from {2}. + /// + internal static string ApplicationPartFactory_InvalidFactoryType + { + get => GetString("ApplicationPartFactory_InvalidFactoryType"); + } + + /// + /// Type {0} specified by {1} is invalid. Type specified by {1} must derive from {2}. + /// + internal static string FormatApplicationPartFactory_InvalidFactoryType(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("ApplicationPartFactory_InvalidFactoryType"), p0, p1, p2); + + /// + /// {0} specified on {1} cannot be self referential. + /// + internal static string RelatedAssemblyAttribute_AssemblyCannotReferenceSelf + { + get => GetString("RelatedAssemblyAttribute_AssemblyCannotReferenceSelf"); + } + + /// + /// {0} specified on {1} cannot be self referential. + /// + internal static string FormatRelatedAssemblyAttribute_AssemblyCannotReferenceSelf(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("RelatedAssemblyAttribute_AssemblyCannotReferenceSelf"), p0, p1); + + /// + /// Related assembly '{0}' specified by assembly '{1}' could not be found in the directory {2}. Related assemblies must be co-located with the specifying assemblies. + /// + internal static string RelatedAssemblyAttribute_CouldNotBeFound + { + get => GetString("RelatedAssemblyAttribute_CouldNotBeFound"); + } + + /// + /// Related assembly '{0}' specified by assembly '{1}' could not be found in the directory {2}. Related assemblies must be co-located with the specifying assemblies. + /// + internal static string FormatRelatedAssemblyAttribute_CouldNotBeFound(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("RelatedAssemblyAttribute_CouldNotBeFound"), p0, p1, p2); + + /// + /// Each related assembly must be declared by exactly one assembly. The assembly '{0}' was declared as related assembly by the following: + /// + internal static string ApplicationAssembliesProvider_DuplicateRelatedAssembly + { + get => GetString("ApplicationAssembliesProvider_DuplicateRelatedAssembly"); + } + + /// + /// Each related assembly must be declared by exactly one assembly. The assembly '{0}' was declared as related assembly by the following: + /// + internal static string FormatApplicationAssembliesProvider_DuplicateRelatedAssembly(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ApplicationAssembliesProvider_DuplicateRelatedAssembly"), p0); + + /// + /// Assembly '{0}' declared as a related assembly by assembly '{1}' cannot define additional related assemblies. + /// + internal static string ApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional + { + get => GetString("ApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional"); + } + + /// + /// Assembly '{0}' declared as a related assembly by assembly '{1}' cannot define additional related assemblies. + /// + internal static string FormatApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ApplicationAssembliesProvider_RelatedAssemblyCannotDefineAdditional"), p0, p1); + + /// + /// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the '{1}' parameter a non-null default value. + /// + internal static string ComplexTypeModelBinder_NoParameterlessConstructor_ForParameter + { + get => GetString("ComplexTypeModelBinder_NoParameterlessConstructor_ForParameter"); + } + + /// + /// Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the '{1}' parameter a non-null default value. + /// + internal static string FormatComplexTypeModelBinder_NoParameterlessConstructor_ForParameter(object p0, object p1) + => 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: + /// + internal static string ApiController_MultipleBodyParametersFound + { + get => GetString("ApiController_MultipleBodyParametersFound"); + } + + /// + /// 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: + /// + internal static string FormatApiController_MultipleBodyParametersFound(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("ApiController_MultipleBodyParametersFound"), p0, p1, p2, p3); + + 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/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectResult.cs new file mode 100644 index 0000000000..6aae329d12 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectResult.cs @@ -0,0 +1,156 @@ +// Copyright (c) .NET 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.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that returns a Found (302), Moved Permanently (301), Temporary Redirect (307), + /// or Permanent Redirect (308) response with a Location header to the supplied URL. + /// + public class RedirectResult : ActionResult, IKeepTempDataResult + { + private string _url; + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The local URL to redirect to. + public RedirectResult(string url) + : this(url, permanent: false) + { + if (url == null) + { + throw new ArgumentNullException(nameof(url)); + } + } + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The URL to redirect to. + /// Specifies whether the redirect should be permanent (301) or temporary (302). + public RedirectResult(string url, bool permanent) + : this(url, permanent, preserveMethod: false) + { + } + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The URL to redirect to. + /// Specifies whether the redirect should be permanent (301) or temporary (302). + /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method. + public RedirectResult(string url, bool permanent, bool preserveMethod) + { + if (url == null) + { + throw new ArgumentNullException(nameof(url)); + } + + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + Permanent = permanent; + PreserveMethod = preserveMethod; + Url = url; + } + + /// + /// Gets or sets the value that specifies that the redirect should be permanent if true or temporary if false. + /// + public bool Permanent { get; set; } + + /// + /// Gets or sets an indication that the redirect preserves the initial request method. + /// + public bool PreserveMethod { get; set; } + + /// + /// Gets or sets the URL to redirect to. + /// + public string Url + { + get => _url; + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(value)); + } + + _url = value; + } + } + + /// + /// Gets or sets the for this result. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + public override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var executor = context.HttpContext.RequestServices.GetRequiredService>(); + return executor.ExecuteAsync(context, this); + } + +#pragma warning disable CS0809 + [Obsolete("This implementation will be removed in a future release, use ExecuteResultAsync.")] + public override void ExecuteResult(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var services = context.HttpContext.RequestServices; + var urlHelperFactory = services.GetRequiredService(); + var logger = services.GetRequiredService>(); + + var urlHelper = UrlHelper ?? urlHelperFactory.GetUrlHelper(context); + + // IsLocalUrl is called to handle URLs starting with '~/'. + var destinationUrl = Url; + if (urlHelper.IsLocalUrl(destinationUrl)) + { + destinationUrl = urlHelper.Content(Url); + } + + logger.RedirectResultExecuting(destinationUrl); + + if (PreserveMethod) + { + context.HttpContext.Response.StatusCode = Permanent ? + StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect; + context.HttpContext.Response.Headers[HeaderNames.Location] = destinationUrl; + } + else + { + context.HttpContext.Response.Redirect(destinationUrl, Permanent); + } + } +#pragma warning restore CS0809 + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs new file mode 100644 index 0000000000..7e48ac38c3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.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; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that returns a Found (302), Moved Permanently (301), Temporary Redirect (307), + /// or Permanent Redirect (308) response with a Location header. + /// Targets a controller action. + /// + public class RedirectToActionResult : ActionResult, IKeepTempDataResult + { + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + public RedirectToActionResult( + string actionName, + string controllerName, + object routeValues) + : this(actionName, controllerName, routeValues, permanent: false) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + public RedirectToActionResult( + string actionName, + string controllerName, + object routeValues, + string fragment) + : this(actionName, controllerName, routeValues, permanent: false, fragment: fragment) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + public RedirectToActionResult( + string actionName, + string controllerName, + object routeValues, + bool permanent) + : this(actionName, controllerName, routeValues, permanent, fragment: null) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method. + public RedirectToActionResult( + string actionName, + string controllerName, + object routeValues, + bool permanent, + bool preserveMethod) + : this(actionName, controllerName, routeValues, permanent, preserveMethod, fragment: null) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + /// The fragment to add to the URL. + public RedirectToActionResult( + string actionName, + string controllerName, + object routeValues, + bool permanent, + string fragment) + : this(actionName, controllerName, routeValues, permanent, preserveMethod: false, fragment: fragment) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + /// If set to true, make the temporary redirect (307) and permanent redirect (308) preserve the initial request method. + /// The fragment to add to the URL. + public RedirectToActionResult( + string actionName, + string controllerName, + object routeValues, + bool permanent, + bool preserveMethod, + string fragment) + { + ActionName = actionName; + ControllerName = controllerName; + RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); + Permanent = permanent; + PreserveMethod = preserveMethod; + Fragment = fragment; + } + + /// + /// Gets or sets the used to generate URLs. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + /// Gets or sets the name of the action to use for generating the URL. + /// + public string ActionName { get; set; } + + /// + /// Gets or sets the name of the controller to use for generating the URL. + /// + public string ControllerName { get; set; } + + /// + /// Gets or sets the route data to use for generating the URL. + /// + public RouteValueDictionary RouteValues { get; set; } + + /// + /// Gets or sets an indication that the redirect is permanent. + /// + public bool Permanent { get; set; } + + /// + /// Gets or sets an indication that the redirect preserves the initial request method. + /// + public bool PreserveMethod { get; set; } + + /// + /// Gets or sets the fragment to add to the URL. + /// + public string Fragment { get; set; } + + /// + public override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var executor = context.HttpContext.RequestServices.GetRequiredService>(); + return executor.ExecuteAsync(context, this); + } + +#pragma warning disable CS0809 + [Obsolete("This implementation will be removed in a future release, use ExecuteResultAsync.")] + public override void ExecuteResult(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var services = context.HttpContext.RequestServices; + var urlHelperFactory = services.GetRequiredService(); + var logger = services.GetRequiredService>(); + + var urlHelper = UrlHelper ?? urlHelperFactory.GetUrlHelper(context); + + var destinationUrl = urlHelper.Action( + ActionName, + ControllerName, + RouteValues, + protocol: null, + host: null, + fragment: Fragment); + if (string.IsNullOrEmpty(destinationUrl)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + logger.RedirectToActionResultExecuting(destinationUrl); + + if (PreserveMethod) + { + context.HttpContext.Response.StatusCode = Permanent ? + StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect; + context.HttpContext.Response.Headers[HeaderNames.Location] = destinationUrl; + } + else + { + context.HttpContext.Response.Redirect(destinationUrl, Permanent); + } + } +#pragma warning restore CS0809 + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToPageResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToPageResult.cs new file mode 100644 index 0000000000..4728a4a08c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToPageResult.cs @@ -0,0 +1,267 @@ +// Copyright (c) .NET 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.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that returns a Found (302) + /// or Moved Permanently (301) response with a Location header. + /// Targets a registered route. + /// + public class RedirectToPageResult : ActionResult, IKeepTempDataResult + { + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The page to redirect to. + public RedirectToPageResult(string pageName) + : this(pageName, routeValues: null) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The page to redirect to. + /// The page handler to redirect to. + public RedirectToPageResult(string pageName, string pageHandler) + : this(pageName, pageHandler, routeValues: null) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The page to redirect to. + /// The parameters for the route. + public RedirectToPageResult(string pageName, object routeValues) + : this(pageName, pageHandler: null, routeValues: routeValues, permanent: false) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The page to redirect to. + /// The page handler to redirect to. + /// The parameters for the route. + public RedirectToPageResult(string pageName, string pageHandler, object routeValues) + : this(pageName, pageHandler, routeValues, permanent: false) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for the page. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + public RedirectToPageResult( + string pageName, + string pageHandler, + object routeValues, + bool permanent) + : this(pageName, pageHandler, routeValues, permanent, fragment: null) + { + } + + /// + /// Initializes a new instance of the with the values provided. + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for the page. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method. + public RedirectToPageResult( + string pageName, + string pageHandler, + object routeValues, + bool permanent, + bool preserveMethod) + : this(pageName, pageHandler, routeValues, permanent, preserveMethod, fragment: null) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for the route. + /// The fragment to add to the URL. + public RedirectToPageResult( + string pageName, + string pageHandler, + object routeValues, + string fragment) + : this(pageName, pageHandler, routeValues, permanent: false, fragment: fragment) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for the page. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + /// The fragment to add to the URL. + public RedirectToPageResult( + string pageName, + string pageHandler, + object routeValues, + bool permanent, + string fragment) + : this(pageName, pageHandler, routeValues, permanent, preserveMethod: false, fragment: fragment) + { + + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for the page. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method. + /// The fragment to add to the URL. + public RedirectToPageResult( + string pageName, + string pageHandler, + object routeValues, + bool permanent, + bool preserveMethod, + string fragment) + { + PageName = pageName; + PageHandler = pageHandler; + RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); + PreserveMethod = preserveMethod; + Permanent = permanent; + Fragment = fragment; + } + + /// + /// Gets or sets the used to generate URLs. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + /// Gets or sets the name of the page to route to. + /// + public string PageName { get; set; } + + /// + /// Gets or sets the page handler to redirect to. + /// + public string PageHandler { get; set; } + + /// + /// Gets or sets the route data to use for generating the URL. + /// + public RouteValueDictionary RouteValues { get; set; } + + /// + /// Gets or sets an indication that the redirect is permanent. + /// + public bool Permanent { get; set; } + + /// + /// Gets or sets an indication that the redirect preserves the initial request method. + /// + public bool PreserveMethod { get; set; } + + /// + /// Gets or sets the fragment to add to the URL. + /// + public string Fragment { get; set; } + + /// + /// Gets or sets the protocol for the URL, such as "http" or "https". + /// + public string Protocol { get; set; } + + /// + /// Gets or sets the host name of the URL. + /// + public string Host { get; set; } + + /// + public override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var executor = context.HttpContext.RequestServices.GetRequiredService>(); + return executor.ExecuteAsync(context, this); + } + +#pragma warning disable CS0809 + [Obsolete("This implementation will be removed in a future release, use ExecuteResultAsync.")] + public override void ExecuteResult(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var services = context.HttpContext.RequestServices; + var urlHelperFactory = services.GetRequiredService(); + var logger = services.GetRequiredService>(); + + var urlHelper = UrlHelper ?? urlHelperFactory.GetUrlHelper(context); + var destinationUrl = urlHelper.Page( + PageName, + PageHandler, + RouteValues, + Protocol, + Host, + fragment: Fragment); + + if (string.IsNullOrEmpty(destinationUrl)) + { + throw new InvalidOperationException(Resources.FormatNoRoutesMatchedForPage(PageName)); + } + + logger.RedirectToPageResultExecuting(PageName); + + if (PreserveMethod) + { + context.HttpContext.Response.StatusCode = Permanent ? + StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect; + context.HttpContext.Response.Headers[HeaderNames.Location] = destinationUrl; + } + else + { + context.HttpContext.Response.Redirect(destinationUrl, Permanent); + } + } +#pragma warning restore CS0809 + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs new file mode 100644 index 0000000000..500d8f3d2f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs @@ -0,0 +1,219 @@ +// Copyright (c) .NET 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.Http; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that returns a Found (302), Moved Permanently (301), Temporary Redirect (307), + /// or Permanent Redirect (308) response with a Location header. + /// Targets a registered route. + /// + public class RedirectToRouteResult : ActionResult, IKeepTempDataResult + { + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The parameters for the route. + public RedirectToRouteResult(object routeValues) + : this(routeName: null, routeValues: routeValues) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the route. + /// The parameters for the route. + public RedirectToRouteResult( + string routeName, + object routeValues) + : this(routeName, routeValues, permanent: false) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the route. + /// The parameters for the route. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + public RedirectToRouteResult( + string routeName, + object routeValues, + bool permanent) + : this(routeName, routeValues, permanent, fragment: null) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the route. + /// The parameters for the route. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method. + public RedirectToRouteResult( + string routeName, + object routeValues, + bool permanent, + bool preserveMethod) + : this(routeName, routeValues, permanent, preserveMethod, fragment: null) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the route. + /// The parameters for the route. + /// The fragment to add to the URL. + public RedirectToRouteResult( + string routeName, + object routeValues, + string fragment) + : this(routeName, routeValues, permanent: false, fragment: fragment) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the route. + /// The parameters for the route. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + /// The fragment to add to the URL. + public RedirectToRouteResult( + string routeName, + object routeValues, + bool permanent, + string fragment) + : this(routeName, routeValues, permanent, preserveMethod: false, fragment: fragment) + { + } + + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the route. + /// The parameters for the route. + /// If set to true, makes the redirect permanent (301). Otherwise a temporary redirect is used (302). + /// If set to true, make the temporary redirect (307) or permanent redirect (308) preserve the initial request method. + /// The fragment to add to the URL. + public RedirectToRouteResult( + string routeName, + object routeValues, + bool permanent, + bool preserveMethod, + string fragment) + { + RouteName = routeName; + RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); + PreserveMethod = preserveMethod; + Permanent = permanent; + Fragment = fragment; + } + + /// + /// Gets or sets the used to generate URLs. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + /// Gets or sets the name of the route to use for generating the URL. + /// + public string RouteName { get; set; } + + /// + /// Gets or sets the route data to use for generating the URL. + /// + public RouteValueDictionary RouteValues { get; set; } + + /// + /// Gets or sets an indication that the redirect is permanent. + /// + public bool Permanent { get; set; } + + /// + /// Gets or sets an indication that the redirect preserves the initial request method. + /// + public bool PreserveMethod { get; set; } + + /// + /// Gets or sets the fragment to add to the URL. + /// + public string Fragment { get; set; } + + /// + public override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var executor = context.HttpContext.RequestServices.GetRequiredService>(); + return executor.ExecuteAsync(context, this); + } + +#pragma warning disable CS0809 + [Obsolete("This implementation will be removed in a future release, use ExecuteResultAsync.")] + public override void ExecuteResult(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var services = context.HttpContext.RequestServices; + var urlHelperFactory = services.GetRequiredService(); + var logger = services.GetRequiredService>(); + + var urlHelper = UrlHelper ?? urlHelperFactory.GetUrlHelper(context); + + var destinationUrl = urlHelper.RouteUrl( + RouteName, + RouteValues, + protocol: null, + host: null, + fragment: Fragment); + if (string.IsNullOrEmpty(destinationUrl)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + logger.RedirectToRouteResultExecuting(destinationUrl, RouteName); + + if (PreserveMethod) + { + context.HttpContext.Response.StatusCode = Permanent ? + StatusCodes.Status308PermanentRedirect : StatusCodes.Status307TemporaryRedirect; + context.HttpContext.Response.Headers[HeaderNames.Location] = destinationUrl; + } + else + { + context.HttpContext.Response.Redirect(destinationUrl, Permanent); + } + } +#pragma warning restore CS0809 + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequestFormLimitsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequestFormLimitsAttribute.cs new file mode 100644 index 0000000000..d1f1c73578 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequestFormLimitsAttribute.cs @@ -0,0 +1,154 @@ +// Copyright (c) .NET 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 Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Sets the specified limits to the . + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class RequestFormLimitsAttribute : Attribute, IFilterFactory, IOrderedFilter + { + /// + /// Gets the order value for determining the order of execution of filters. Filters execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Filters are executed in an ordering determined by an ascending sort of the property. + /// + /// + /// The default Order for this attribute is 900 because it must run before ValidateAntiForgeryTokenAttribute and + /// after any filter which does authentication or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400). + /// + /// + /// Look at for more detailed info. + /// + /// + public int Order { get; set; } = 900; + + /// + public bool IsReusable => true; + + // Internal for unit testing + internal FormOptions FormOptions { get; } = new FormOptions(); + + /// + /// Enables full request body buffering. Use this if multiple components need to read the raw stream. + /// The default value is false. + /// + public bool BufferBody + { + get => FormOptions.BufferBody; + set => FormOptions.BufferBody = value; + } + + /// + /// If is enabled, this many bytes of the body will be buffered in memory. + /// If this threshold is exceeded then the buffer will be moved to a temp file on disk instead. + /// This also applies when buffering individual multipart section bodies. + /// + public int MemoryBufferThreshold + { + get => FormOptions.MemoryBufferThreshold; + set => FormOptions.MemoryBufferThreshold = value; + } + + /// + /// If is enabled, this is the limit for the total number of bytes that will + /// be buffered. Forms that exceed this limit will throw an when parsed. + /// + public long BufferBodyLengthLimit + { + get => FormOptions.BufferBodyLengthLimit; + set => FormOptions.BufferBodyLengthLimit = value; + } + + /// + /// A limit for the number of form entries to allow. + /// Forms that exceed this limit will throw an when parsed. + /// + public int ValueCountLimit + { + get => FormOptions.ValueCountLimit; + set => FormOptions.ValueCountLimit = value; + } + + /// + /// A limit on the length of individual keys. Forms containing keys that exceed this limit will + /// throw an when parsed. + /// + public int KeyLengthLimit + { + get => FormOptions.KeyLengthLimit; + set => FormOptions.KeyLengthLimit = value; + } + + /// + /// A limit on the length of individual form values. Forms containing values that exceed this + /// limit will throw an when parsed. + /// + public int ValueLengthLimit + { + get => FormOptions.ValueLengthLimit; + set => FormOptions.ValueLengthLimit = value; + } + + /// + /// A limit for the length of the boundary identifier. Forms with boundaries that exceed this + /// limit will throw an when parsed. + /// + public int MultipartBoundaryLengthLimit + { + get => FormOptions.MultipartBoundaryLengthLimit; + set => FormOptions.MultipartBoundaryLengthLimit = value; + } + + /// + /// A limit for the number of headers to allow in each multipart section. Headers with the same name will + /// be combined. Form sections that exceed this limit will throw an + /// when parsed. + /// + public int MultipartHeadersCountLimit + { + get => FormOptions.MultipartHeadersCountLimit; + set => FormOptions.MultipartHeadersCountLimit = value; + } + + /// + /// A limit for the total length of the header keys and values in each multipart section. + /// Form sections that exceed this limit will throw an when parsed. + /// + public int MultipartHeadersLengthLimit + { + get => FormOptions.MultipartHeadersLengthLimit; + set => FormOptions.MultipartHeadersLengthLimit = value; + } + + /// + /// A limit for the length of each multipart body. Forms sections that exceed this limit will throw an + /// when parsed. + /// + public long MultipartBodyLengthLimit + { + get => FormOptions.MultipartBodyLengthLimit; + set => FormOptions.MultipartBodyLengthLimit = value; + } + + /// + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + var filter = serviceProvider.GetRequiredService(); + filter.FormOptions = FormOptions; + return filter; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequestSizeLimitAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequestSizeLimitAttribute.cs new file mode 100644 index 0000000000..95063ff857 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequestSizeLimitAttribute.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.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Sets the request body size limit to the specified size. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class RequestSizeLimitAttribute : Attribute, IFilterFactory, IOrderedFilter + { + private readonly long _bytes; + + /// + /// Creates a new instance of . + /// + /// The request body size limit. + public RequestSizeLimitAttribute(long bytes) + { + _bytes = bytes; + } + + /// + /// Gets the order value for determining the order of execution of filters. Filters execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Filters are executed in an ordering determined by an ascending sort of the property. + /// + /// + /// The default Order for this attribute is 900 because it must run before ValidateAntiForgeryTokenAttribute and + /// after any filter which does authentication or login in order to allow them to behave as expected (ie Unauthenticated or Redirect instead of 400). + /// + /// + /// Look at for more detailed info. + /// + /// + public int Order { get; set; } = 900; + + /// + public bool IsReusable => true; + + /// + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + var filter = serviceProvider.GetRequiredService(); + filter.Bytes = _bytes; + return filter; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequireHttpsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequireHttpsAttribute.cs new file mode 100644 index 0000000000..f8f9c19c01 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequireHttpsAttribute.cs @@ -0,0 +1,106 @@ +// Copyright (c) .NET 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; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An authorization filter that confirms requests are received over HTTPS. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)] + public class RequireHttpsAttribute : Attribute, IAuthorizationFilter, IOrderedFilter + { + private bool? _permanent; + + /// + /// Specifies whether a permanent redirect, 301 Moved Permanently, + /// should be used instead of a temporary redirect, 302 Found. + /// + public bool Permanent + { + get { return _permanent ?? false; } + set { _permanent = value; } + } + + /// + /// Default is int.MinValue + 50 to run this early. + public int Order { get; set; } = int.MinValue + 50; + + /// + /// Called early in the filter pipeline to confirm request is authorized. Confirms requests are received over + /// HTTPS. Takes no action for HTTPS requests. Otherwise if it was a GET request, sets + /// to a result which will redirect the client to the HTTPS + /// version of the request URI. Otherwise, sets to a result + /// which will set the status code to 403 (Forbidden). + /// + /// + public virtual void OnAuthorization(AuthorizationFilterContext filterContext) + { + if (filterContext == null) + { + throw new ArgumentNullException(nameof(filterContext)); + } + + if (!filterContext.HttpContext.Request.IsHttps) + { + HandleNonHttpsRequest(filterContext); + } + } + + /// + /// Called from if the request is not received over HTTPS. Expectation is + /// will not be null after this method returns. + /// + /// The to update. + /// + /// If it was a GET request, default implementation sets to a + /// result which will redirect the client to the HTTPS version of the request URI. Otherwise, default + /// implementation sets to a result which will set the status + /// code to 403 (Forbidden). + /// + protected virtual void HandleNonHttpsRequest(AuthorizationFilterContext filterContext) + { + // only redirect for GET requests, otherwise the browser might not propagate the verb and request + // body correctly. + if (!string.Equals(filterContext.HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase)) + { + filterContext.Result = new StatusCodeResult(StatusCodes.Status403Forbidden); + } + else + { + var optionsAccessor = filterContext.HttpContext.RequestServices.GetRequiredService>(); + + var request = filterContext.HttpContext.Request; + + var host = request.Host; + if (optionsAccessor.Value.SslPort.HasValue && optionsAccessor.Value.SslPort > 0) + { + // a specific SSL port is specified + host = new HostString(host.Host, optionsAccessor.Value.SslPort.Value); + } + else + { + // clear the port + host = new HostString(host.Host); + } + + var permanentValue = _permanent ?? optionsAccessor.Value.RequireHttpsPermanent; + + var newUrl = string.Concat( + "https://", + host.ToUriComponent(), + request.PathBase.ToUriComponent(), + request.Path.ToUriComponent(), + request.QueryString.ToUriComponent()); + + // redirect to HTTPS version of page + filterContext.Result = new RedirectResult(newUrl, permanentValue); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx new file mode 100644 index 0000000000..909febc6b4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -0,0 +1,445 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 argument '{0}' is invalid. Media types which match all types or match all subtypes are not supported. + + + The content-type '{0}' added in the '{1}' property is invalid. Media types which match all types or match all subtypes are not supported. + + + The method '{0}' on type '{1}' returned an instance of '{2}'. Make sure to call Unwrap on the returned value to avoid unobserved faulted Task. + + + The method '{0}' on type '{1}' returned a Task instance even though it is not an asynchronous method. + + + An action invoker could not be created for action '{0}'. + + + The action descriptor must be of type '{0}'. + + + Value cannot be null or empty. + + + The '{0}' property of '{1}' must not be null. + + + The '{0}' method of type '{1}' cannot return a null value. + + + The value '{0}' is invalid. + + + The passed expression of expression node type '{0}' is invalid. Only simple member access expressions for model properties are supported. + + + No route matches the supplied values. + + + If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}. + + + If an {0} cancels execution by setting the {1} property of {2} to 'true', then it cannot call the next filter by invoking {3}. + + + The type provided to '{0}' must implement '{1}'. + + + Cannot return null from an action method with a return type of '{0}'. + + + The type '{0}' must derive from '{1}'. + + + No encoding found for input formatter '{0}'. There must be at least one supported encoding registered in order for the formatter to read content. + + + Unsupported content type '{0}'. + + + No supported media type registered for output formatter '{0}'. There must be at least one supported media type registered in order for the output formatter to write content. + + + The following errors occurred with attribute routing information:{0}{0}{1} + {0} is the newline. {1} is the formatted list of errors using AttributeRoute_IndividualErrorMessage + + + The attribute route '{0}' cannot contain a parameter named '{{{1}}}'. Use '[{1}]' in the route template to insert the value '{2}'. + + + For action: '{0}'{1}Error: {2} + {1} is the newline. + + + An empty replacement token ('[]') is not allowed. + + + Token delimiters ('[', ']') are imbalanced. + + + The route template '{0}' has invalid syntax. {1} + {1} is the specific error message. + + + While processing template '{0}', a replacement value for the token '{1}' could not be found. Available tokens: '{2}'. To use a '[' or ']' as a literal string in a route or within a constraint, use '[[' or ']]' instead. + + + A replacement token is not closed. + + + An unescaped '[' token is not allowed inside of a replacement token. Use '[[' to escape. + + + Unable to find the required services. Please add all the required services by calling '{0}.{1}' inside the call to '{2}' in the application startup code. + + + Action: '{0}' - Template: '{1}' + Formats an action descriptor display name and it's associated template. + + + Attribute routes with the same name '{0}' must have the same template:{1}{2} + {0} is the name of the attribute route, {1} is the newline, {2} is the list of errors formatted using ActionDescriptor_WithNamedAttributeRouteAndDifferentTemplate + + + Error {0}:{1}{2} + {0} is the error number, {1} is Environment.NewLine {2} is the error message + + + A method '{0}' must not define attribute routed actions and non attribute routed actions at the same time:{1}{2}{1}{1}Use 'AcceptVerbsAttribute' to create a single route that allows multiple HTTP verbs and defines a route, or set a route template in all attributes that constrain HTTP verbs. + {0} is the MethodInfo.FullName, {1} is Environment.NewLine, {2} is the formatted list of actions defined by that method info. + + + Action: '{0}' - Route Template: '{1}' - HTTP Verbs: '{2}' + + + (none) + + + Multiple actions matched. The following actions matched route data and had all constraints satisfied:{0}{0}{1} + 0 is the newline - 1 is a newline separate list of action display names + + + Could not find file: {0} + {0} is the value for the provided path + + + The input was not valid. + + + If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}. + + + If the '{0}' property is not set to true, '{1}' property must be specified. + + + The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer. + + + The media type "{0}" is not valid. MediaTypes containing wildcards (*) are not allowed in formatter mappings. + + + The format provided is invalid '{0}'. A format must be a non-empty file-extension, optionally prefixed with a '.' character. + + + The '{0}' cache profile is not defined. + + + The model's runtime type '{0}' is not assignable to the type '{1}'. + + + The type '{0}' cannot be activated by '{1}' because it is either a value type, an interface, an abstract class or an open generic type. + + + The type '{0}' must implement '{1}' to be used as a model binder. + + + The provided binding source '{0}' is a composite. '{1}' requires that the source must represent a single type of input. + + + The provided binding source '{0}' is a greedy data source. '{1}' does not support greedy data sources. + + + The property {0}.{1} could not be found. + + + The key '{0}' is invalid JQuery syntax because it is missing a closing bracket. + + + A value is required. + + + The binding context has a null Model, but this binder requires a non-null model of type '{0}'. + + + The binding context has a Model of type '{0}', but this binder can only operate on models of type '{1}'. + + + The binding context cannot have a null ModelMetadata. + + + A value for the '{0}' property was not provided. + + + A non-empty request body is required. + + + The parameter conversion from type '{0}' to type '{1}' failed because no type converter can convert between these types. + + + Path '{0}' was not rooted. + {0} is the path which wasn't rooted + + + The supplied URL is not local. A URL with an absolute path is considered local if it does not have a host/authority part. URLs using virtual paths ('~/') are also local. + + + The argument '{0}' is invalid. Empty or null formats are not supported. + + + "Invalid values '{0}'." + + + The value '{0}' is not valid for {1}. + + + The value '{0}' is not valid. + + + The supplied value is invalid for {0}. + + + The supplied value is invalid. + + + The value '{0}' is invalid. + + + The field {0} must be a number. + + + The field must be a number. + + + The list of '{0}' must not be empty. Add at least one supported encoding. + + + The list of '{0}' must not be empty. Add at least one supported encoding. + + + '{0}' is not supported by '{1}'. Use '{2}' instead. + + + No media types found in '{0}.{1}'. Add at least one media type to the list of supported media types. + + + Could not create a model binder for model object of type '{0}'. + + + '{0}.{1}' must not be empty. At least one '{2}' is required to bind from the body. + + + '{0}.{1}' must not be empty. At least one '{2}' is required to model bind. + + + '{0}.{1}' must not be empty. At least one '{2}' is required to format a response. + + + Multiple overloads of method '{0}' are not supported. + + + A public method named '{0}' could not be found in the '{1}' type. + + + Could not find '{0}' in the feature list. + + + The '{0}' property cannot be null. + + + The '{0}' method in the type '{1}' must have a return type of '{2}'. + + + Could not resolve a service of type '{0}' for the parameter '{1}' of method '{2}' on type '{3}'. + + + An {0} cannot be created without a valid instance of {1}. + + + The '{0}' cannot bind to a model of type '{1}'. Change the model type to '{2}' instead. + + + '{0}' requires the response cache middleware. + + + A duplicate entry for library reference {0} was found. Please check that all package references in all projects use the same casing for the same package references. + {0} is the dependency name. + + + Unable to create an instance of type '{0}'. The type specified in {1} must not be abstract and must have a parameterless constructor. + 0 is the type to configure. 1 is the name of the parameter, configurationType. + + + '{0}' and '{1}' are out of bounds for the string. + '{0}' and '{1}' are the parameters which combine to be out of bounds. + + + Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. + + + Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, set the '{1}' property to a non-null value in the '{2}' constructor. + + + 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. + + + One or more validation errors occurred. + + + Action '{0}' does not have an attribute route. Action methods on controllers annotated with {1} must be attribute routed. + + + No file provider has been configured to process the supplied file. + + + Type {0} specified by {1} is invalid. Type specified by {1} must derive from {2}. + + + {0} specified on {1} cannot be self referential. + + + Related assembly '{0}' specified by assembly '{1}' could not be found in the directory {2}. Related assemblies must be co-located with the specifying assemblies. + + + Each related assembly must be declared by exactly one assembly. The assembly '{0}' was declared as related assembly by the following: + + + Assembly '{0}' declared as a related assembly by assembly '{1}' cannot define additional related assemblies. + + + Could not create an instance of type '{0}'. Model bound complex types must not be abstract or value types and must have a parameterless constructor. Alternatively, give the '{1}' parameter a non-null default value. + + + 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: + + \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheAttribute.cs new file mode 100644 index 0000000000..a3e2d6aaf4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheAttribute.cs @@ -0,0 +1,136 @@ +// Copyright (c) .NET 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; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies the parameters necessary for setting appropriate headers in response caching. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class ResponseCacheAttribute : Attribute, IFilterFactory, IOrderedFilter + { + // A nullable-int cannot be used as an Attribute parameter. + // Hence this nullable-int is present to back the Duration property. + // The same goes for nullable-ResponseCacheLocation and nullable-bool. + private int? _duration; + private ResponseCacheLocation? _location; + private bool? _noStore; + + /// + /// Gets or sets the duration in seconds for which the response is cached. + /// This sets "max-age" in "Cache-control" header. + /// + public int Duration + { + get => _duration ?? 0; + set => _duration = value; + } + + /// + /// Gets or sets the location where the data from a particular URL must be cached. + /// + public ResponseCacheLocation Location + { + get => _location ?? ResponseCacheLocation.Any; + set => _location = value; + } + + /// + /// Gets or sets the value which determines whether the data should be stored or not. + /// When set to , it sets "Cache-control" header to "no-store". + /// Ignores the "Location" parameter for values other than "None". + /// Ignores the "duration" parameter. + /// + public bool NoStore + { + get => _noStore ?? false; + set => _noStore = value; + } + + /// + /// Gets or sets the value for the Vary response header. + /// + public string VaryByHeader { get; set; } + + /// + /// Gets or sets the query keys to vary by. + /// + /// + /// requires the response cache middleware. + /// + public string[] VaryByQueryKeys { get; set; } + + /// + /// Gets or sets the value of the cache profile name. + /// + public string CacheProfileName { get; set; } + + /// + public int Order { get; set; } + + /// + public bool IsReusable => true; + + /// + /// Gets the for this attribute. + /// + /// + public CacheProfile GetCacheProfile(MvcOptions options) + { + CacheProfile selectedProfile = null; + if (CacheProfileName != null) + { + options.CacheProfiles.TryGetValue(CacheProfileName, out selectedProfile); + if (selectedProfile == null) + { + throw new InvalidOperationException(Resources.FormatCacheProfileNotFound(CacheProfileName)); + } + } + + // If the ResponseCacheAttribute parameters are set, + // then it must override the values from the Cache Profile. + // The below expression first checks if the duration is set by the attribute's parameter. + // If absent, it checks the selected cache profile (Note: There can be no cache profile as well) + // The same is the case for other properties. + _duration = _duration ?? selectedProfile?.Duration; + _noStore = _noStore ?? selectedProfile?.NoStore; + _location = _location ?? selectedProfile?.Location; + VaryByHeader = VaryByHeader ?? selectedProfile?.VaryByHeader; + VaryByQueryKeys = VaryByQueryKeys ?? selectedProfile?.VaryByQueryKeys; + + return new CacheProfile + { + Duration = _duration, + Location = _location, + NoStore = _noStore, + VaryByHeader = VaryByHeader, + VaryByQueryKeys = VaryByQueryKeys, + }; + } + + /// + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + var loggerFactory = serviceProvider.GetRequiredService(); + var optionsAccessor = serviceProvider.GetRequiredService>(); + var cacheProfile = GetCacheProfile(optionsAccessor.Value); + + // ResponseCacheFilter cannot take any null values. Hence, if there are any null values, + // the properties convert them to their defaults and are passed on. + return new ResponseCacheFilter(cacheProfile, loggerFactory); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheLocation.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheLocation.cs new file mode 100644 index 0000000000..8f9e852de0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheLocation.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. + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Determines the value for the "Cache-control" header in the response. + /// + public enum ResponseCacheLocation + { + /// + /// Cached in both proxies and client. + /// Sets "Cache-control" header to "public". + /// + Any = 0, + /// + /// Cached only in the client. + /// Sets "Cache-control" header to "private". + /// + Client = 1, + /// + /// "Cache-control" and "Pragma" headers are set to "no-cache". + /// + None = 2 + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RouteAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RouteAttribute.cs new file mode 100644 index 0000000000..db42bd2bc1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RouteAttribute.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.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies an attribute route on a controller. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public class RouteAttribute : Attribute, IRouteTemplateProvider + { + private int? _order; + + /// + /// Creates a new with the given route template. + /// + /// The route template. May not be null. + public RouteAttribute(string template) + { + if (template == null) + { + throw new ArgumentNullException(nameof(template)); + } + + Template = template; + } + + /// + public string Template { get; } + + /// + /// Gets the route order. The order determines the order of route execution. Routes with a lower order + /// value are tried first. If an action defines a route by providing an + /// with a non null order, that order is used instead of this value. If neither the action nor the + /// controller defines an order, a default value of 0 is used. + /// + public int Order + { + get { return _order ?? 0; } + set { _order = value; } + } + + /// + int? IRouteTemplateProvider.Order => _order; + + /// + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/HttpMethodAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/HttpMethodAttribute.cs new file mode 100644 index 0000000000..44095ee4ae --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/HttpMethodAttribute.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 System; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + /// + /// Identifies an action that only supports a given set of HTTP methods. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public abstract class HttpMethodAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider + { + private int? _order; + + /// + /// Creates a new with the given + /// set of HTTP methods. + /// The set of supported HTTP methods. + /// + public HttpMethodAttribute(IEnumerable httpMethods) + : this(httpMethods, null) + { + if (httpMethods == null) + { + throw new ArgumentNullException(nameof(httpMethods)); + } + } + + /// + /// Creates a new with the given + /// set of HTTP methods an the given route template. + /// + /// The set of supported methods. + /// The route template. May not be null. + public HttpMethodAttribute(IEnumerable httpMethods, string template) + { + if (httpMethods == null) + { + throw new ArgumentNullException(nameof(httpMethods)); + } + + HttpMethods = httpMethods; + Template = template; + } + + /// + public IEnumerable HttpMethods { get; } + + /// + public string Template { get; } + + /// + /// Gets the route order. The order determines the order of route execution. Routes with a lower + /// order value are tried first. When a route doesn't specify a value, it gets the value of the + /// or a default value of 0 if the + /// doesn't define a value on the controller. + /// + public int Order + { + get { return _order ?? 0; } + set { _order = value; } + } + + /// + int? IRouteTemplateProvider.Order => _order; + + /// + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IActionHttpMethodProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IActionHttpMethodProvider.cs new file mode 100644 index 0000000000..2c5d640ee8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IActionHttpMethodProvider.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 +{ + public interface IActionHttpMethodProvider + { + IEnumerable HttpMethods { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteTemplateProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteTemplateProvider.cs new file mode 100644 index 0000000000..9d644cba37 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteTemplateProvider.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. + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + /// + /// Interface for attributes which can supply a route template for attribute routing. + /// + public interface IRouteTemplateProvider + { + /// + /// The route template. May be null. + /// + string Template { get; } + + /// + /// Gets the route order. The order determines the order of route execution. Routes with a lower + /// order value are tried first. When a route doesn't specify a value, it gets a default value of 0. + /// A null value for the Order property means that the user didn't specify an explicit order for the + /// route. + /// + int? Order { get; } + + /// + /// Gets the route name. The route name can be used to generate a link using a specific route, instead + /// of relying on selection of a route based on the given set of route values. + /// + string Name { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteValueProvider.cs new file mode 100644 index 0000000000..6bd09db26b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteValueProvider.cs @@ -0,0 +1,84 @@ +// Copyright (c) .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; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + /// + /// + /// A metadata interface which specifies a route value which is required for the action selector to + /// choose an action. When applied to an action using attribute routing, the route value will be added + /// to the when the action is selected. + /// + /// + /// When an is used to provide a new route value to an action, all + /// actions in the application must also have a value associated with that key, or have an implicit value + /// of null. See remarks for more details. + /// + /// + /// + /// + /// The typical scheme for action selection in an MVC application is that an action will require the + /// matching values for its and + /// + /// + /// + /// For an action like MyApp.Controllers.HomeController.Index(), in order to be selected, the + /// must contain the values + /// { + /// "action": "Index", + /// "controller": "Home" + /// } + /// + /// + /// If areas are in use in the application (see which implements + /// ) then all actions are consider either in an area by having a + /// non-null area value (specified by or another + /// ) or are considered 'outside' of areas by having the value null. + /// + /// + /// Consider an application with two controllers, each with an Index action method: + /// - MyApp.Controllers.HomeController.Index() + /// - MyApp.Areas.Blog.Controllers.HomeController.Index() + /// where MyApp.Areas.Blog.Controllers.HomeController has an area attribute + /// [Area("Blog")]. + /// + /// For like: + /// { + /// "action": "Index", + /// "controller": "Home" + /// } + /// + /// MyApp.Controllers.HomeController.Index() will be selected. + /// MyApp.Area.Blog.Controllers.HomeController.Index() is not considered eligible because the + /// does not contain the value 'Blog' for 'area'. + /// + /// For like: + /// { + /// "area": "Blog", + /// "action": "Index", + /// "controller": "Home" + /// } + /// + /// MyApp.Area.Blog.Controllers.HomeController.Index() will be selected. + /// MyApp.Controllers.HomeController.Index() is not considered eligible because the route values + /// contain a value for 'area'. MyApp.Controllers.HomeController.Index() cannot match any value + /// for 'area' other than null. + /// + /// + public interface IRouteValueProvider + { + /// + /// The route value key. + /// + string RouteKey { get; } + + /// + /// The route value. If null or empty, requires the route value associated with + /// to be missing or null. + /// + string RouteValue { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IUrlHelperFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IUrlHelperFactory.cs new file mode 100644 index 0000000000..9eecb96115 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IUrlHelperFactory.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.Routing +{ + /// + /// A factory for creating instances. + /// + public interface IUrlHelperFactory + { + /// + /// Gets an for the request associated with . + /// + /// The associated with the current request. + /// An for the request associated with + IUrlHelper GetUrlHelper(ActionContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/KnownRouteValueConstraint.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/KnownRouteValueConstraint.cs new file mode 100644 index 0000000000..a80b3b510c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/KnownRouteValueConstraint.cs @@ -0,0 +1,124 @@ +// Copyright (c) .NET 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.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + public class KnownRouteValueConstraint : IRouteConstraint + { + private RouteValuesCollection _cachedValuesCollection; + + public bool Match( + HttpContext httpContext, + IRouter route, + string routeKey, + 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)); + } + + if (values == null) + { + throw new ArgumentNullException(nameof(values)); + } + + object obj; + if (values.TryGetValue(routeKey, out obj)) + { + var value = obj as string; + if (value != null) + { + var allValues = GetAndCacheAllMatchingValues(routeKey, httpContext); + foreach (var existingValue in allValues) + { + if (string.Equals(value, existingValue, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + } + } + + return false; + } + + private string[] GetAndCacheAllMatchingValues(string routeKey, HttpContext httpContext) + { + var actionDescriptors = GetAndValidateActionDescriptorCollection(httpContext); + var version = actionDescriptors.Version; + var valuesCollection = _cachedValuesCollection; + + if (valuesCollection == null || + version != valuesCollection.Version) + { + var values = new HashSet(StringComparer.OrdinalIgnoreCase); + for (var i = 0; i < actionDescriptors.Items.Count; i++) + { + var action = actionDescriptors.Items[i]; + + string value; + if (action.RouteValues.TryGetValue(routeKey, out value) && + !string.IsNullOrEmpty(value)) + { + values.Add(value); + } + } + + valuesCollection = new RouteValuesCollection(version, values.ToArray()); + _cachedValuesCollection = valuesCollection; + } + + 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) + { + Version = version; + Items = items; + } + + public int Version { get; } + + public string[] Items { get; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/RouteValueAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/RouteValueAttribute.cs new file mode 100644 index 0000000000..4c9d3ead75 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/RouteValueAttribute.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; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + /// + /// + /// An attribute which specifies a required route value for an action or controller. + /// + /// + /// When placed on an action, the route data of a request must match the expectations of the required route data + /// in order for the action to be selected. All other actions without a route value for the given key cannot be + /// selected unless the route data of the request does omits a value matching the key. + /// See for more details and examples. + /// + /// + /// When placed on a controller, unless overridden by the action, the constraint applies to all + /// actions defined by the controller. + /// + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + public abstract class RouteValueAttribute : Attribute, IRouteValueProvider + { + /// + /// Creates a new . + /// + /// The route value key. + /// The expected route value. + protected RouteValueAttribute( + string routeKey, + string routeValue) + { + if (routeKey == null) + { + throw new ArgumentNullException(nameof(routeKey)); + } + + if (routeValue == null) + { + throw new ArgumentNullException(nameof(routeValue)); + } + + RouteKey = routeKey; + RouteValue = routeValue; + } + + /// + public string RouteKey { get; } + + /// + public string RouteValue { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs new file mode 100644 index 0000000000..31a8b4a9fd --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs @@ -0,0 +1,392 @@ +// Copyright (c) .NET 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.Text; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +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 + { + + // 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) + { + 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. + /// + protected HttpContext HttpContext => ActionContext.HttpContext; + + /// + /// Gets the top-level associated with the current request. Generally an + /// implementation. + /// + protected IRouter Router => ActionContext.RouteData.Routers[0]; + + /// + public virtual string Action(UrlActionContext actionContext) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + var valuesDictionary = GetValuesDictionary(actionContext.Values); + + if (actionContext.Action == null) + { + object action; + if (!valuesDictionary.ContainsKey("action") && + AmbientValues.TryGetValue("action", out action)) + { + valuesDictionary["action"] = action; + } + } + else + { + valuesDictionary["action"] = actionContext.Action; + } + + if (actionContext.Controller == null) + { + object controller; + if (!valuesDictionary.ContainsKey("controller") && + AmbientValues.TryGetValue("controller", out controller)) + { + valuesDictionary["controller"] = controller; + } + } + else + { + valuesDictionary["controller"] = actionContext.Controller; + } + + var virtualPathData = GetVirtualPathData(routeName: null, values: valuesDictionary); + return GenerateUrl(actionContext.Protocol, actionContext.Host, virtualPathData, actionContext.Fragment); + } + + /// + 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) + { + if (routeContext == null) + { + throw new ArgumentNullException(nameof(routeContext)); + } + + var valuesDictionary = routeContext.Values as RouteValueDictionary ?? GetValuesDictionary(routeContext.Values); + var virtualPathData = GetVirtualPathData(routeContext.RouteName, valuesDictionary); + return GenerateUrl(routeContext.Protocol, routeContext.Host, virtualPathData, routeContext.Fragment); + } + + /// + /// Gets the for the specified and route + /// . + /// + /// The name of the route that is used to generate the . + /// + /// + /// The . The uses these values, in combination with + /// , to generate the URL. + /// + /// The . + protected virtual VirtualPathData GetVirtualPathData(string routeName, RouteValueDictionary values) + { + var context = new VirtualPathContext(HttpContext, AmbientValues, values, routeName); + 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. + /// + /// The protocol for the URL, such as "http" or "https". + /// The host name for the URL. + /// The . + /// The fragment for the URL. + /// 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; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs new file mode 100644 index 0000000000..f9b8006850 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.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.Core; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + /// + /// A default implementation of . + /// + public class UrlHelperFactory : IUrlHelperFactory + { + /// + public IUrlHelper GetUrlHelper(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(Resources.ArgumentCannotBeNullOrEmpty, (nameof(context))); + } + + var httpContext = context.HttpContext; + + if (httpContext == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(ActionContext.HttpContext), + nameof(ActionContext))); + } + + if (httpContext.Items == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(HttpContext.Items), + nameof(HttpContext))); + } + + // Perf: Create only one UrlHelper per context + object value; + if (httpContext.Items.TryGetValue(typeof(IUrlHelper), out value) && value is IUrlHelper) + { + return (IUrlHelper)value; + } + + var urlHelper = new UrlHelper(context); + httpContext.Items[typeof(IUrlHelper)] = urlHelper; + + return urlHelper; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SerializableError.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SerializableError.cs new file mode 100644 index 0000000000..77adaeae1e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SerializableError.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET 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.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Defines a serializable container for storing ModelState information. + /// This information is stored as key/value pairs. + /// + public sealed class SerializableError : Dictionary + { + /// + /// Initializes a new instance of the class. + /// + public SerializableError() + : base(StringComparer.OrdinalIgnoreCase) + { + } + + /// + /// Creates a new instance of . + /// + /// containing the validation errors. + public SerializableError(ModelStateDictionary modelState) + : this() + { + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + if (modelState.IsValid) + { + return; + } + + foreach (var keyModelStatePair in modelState) + { + var key = keyModelStatePair.Key; + var errors = keyModelStatePair.Value.Errors; + if (errors != null && errors.Count > 0) + { + var errorMessages = errors.Select(error => + { + return string.IsNullOrEmpty(error.ErrorMessage) ? + Resources.SerializableError_DefaultError : error.ErrorMessage; + }).ToArray(); + + Add(key, errorMessages); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ServiceFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ServiceFilterAttribute.cs new file mode 100644 index 0000000000..525d1a82bb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ServiceFilterAttribute.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET 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.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A filter that finds another filter in an . + /// + /// + /// + /// Primarily used in calls. + /// + /// + /// Similar to the in that both use constructor injection. Use + /// instead if the filter is not itself a service. + /// + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + [DebuggerDisplay("ServiceFilter: Type={ServiceType} Order={Order}")] + public class ServiceFilterAttribute : Attribute, IFilterFactory, IOrderedFilter + { + /// + /// Instantiates a new instance. + /// + /// The of filter to find. + public ServiceFilterAttribute(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + ServiceType = type; + } + + /// + public int Order { get; set; } + + /// + /// Gets the of filter to find. + /// + public Type ServiceType { get; } + + /// + public bool IsReusable { get; set; } + + /// + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + var service = serviceProvider.GetRequiredService(ServiceType); + + var filter = service as IFilterMetadata; + if (filter == null) + { + throw new InvalidOperationException(Resources.FormatFilterFactoryAttribute_TypeMustImplementIFilter( + typeof(ServiceFilterAttribute).Name, + typeof(IFilterMetadata).Name)); + } + + return filter; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SignInResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SignInResult.cs new file mode 100644 index 0000000000..040bbe040d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SignInResult.cs @@ -0,0 +1,94 @@ +// Copyright (c) .NET 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.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that on execution invokes . + /// + public class SignInResult : ActionResult + { + /// + /// Initializes a new instance of with the + /// specified authentication scheme. + /// + /// The authentication scheme to use when signing in the user. + /// The claims principal containing the user claims. + public SignInResult(string authenticationScheme, ClaimsPrincipal principal) + : this(authenticationScheme, principal, properties: null) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication scheme and . + /// + /// The authentication schemes to use when signing in the user. + /// The claims principal containing the user claims. + /// used to perform the sign-in operation. + public SignInResult(string authenticationScheme, ClaimsPrincipal principal, AuthenticationProperties properties) + { + if (authenticationScheme == null) + { + throw new ArgumentNullException(nameof(authenticationScheme)); + } + + if (principal == null) + { + throw new ArgumentNullException(nameof(principal)); + } + + AuthenticationScheme = authenticationScheme; + Principal = principal; + Properties = properties; + } + + /// + /// Gets or sets the authentication scheme that is used to perform the sign-in operation. + /// + public string AuthenticationScheme { get; set; } + + /// + /// Gets or sets the containing the user claims. + /// + public ClaimsPrincipal Principal { get; set; } + + /// + /// Gets or sets the used to perform the sign-in operation. + /// + public AuthenticationProperties Properties { get; set; } + + /// + public override async Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (AuthenticationScheme == null) + { + throw new InvalidOperationException( + Resources.FormatPropertyOfTypeCannotBeNull( + /* property: */ nameof(AuthenticationScheme), + /* type: */ nameof(SignInResult))); + } + + var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); + var logger = loggerFactory.CreateLogger(); + + logger.SignInResultExecuting(AuthenticationScheme, Principal); + + await context.HttpContext.SignInAsync(AuthenticationScheme, Principal, Properties); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SignOutResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SignOutResult.cs new file mode 100644 index 0000000000..f972b9475c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SignOutResult.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; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that on execution invokes . + /// + public class SignOutResult : ActionResult + { + /// + /// Initializes a new instance of with the default sign out scheme. + /// + public SignOutResult() + : this(Array.Empty()) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication scheme. + /// + /// The authentication scheme to use when signing out the user. + public SignOutResult(string authenticationScheme) + : this(new[] { authenticationScheme }) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication schemes. + /// + /// The authentication schemes to use when signing out the user. + public SignOutResult(IList authenticationSchemes) + : this(authenticationSchemes, properties: null) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication scheme and . + /// + /// The authentication schemes to use when signing out the user. + /// used to perform the sign-out operation. + public SignOutResult(string authenticationScheme, AuthenticationProperties properties) + : this(new[] { authenticationScheme }, properties) + { + } + + /// + /// Initializes a new instance of with the + /// specified authentication schemes and . + /// + /// The authentication scheme to use when signing out the user. + /// used to perform the sign-out operation. + public SignOutResult(IList authenticationSchemes, AuthenticationProperties properties) + { + if (authenticationSchemes == null) + { + throw new ArgumentNullException(nameof(authenticationSchemes)); + } + + AuthenticationSchemes = authenticationSchemes; + Properties = properties; + } + + /// + /// Gets or sets the authentication schemes that are challenged. + /// + public IList AuthenticationSchemes { get; set; } + + /// + /// Gets or sets the used to perform the sign-out operation. + /// + public AuthenticationProperties Properties { get; set; } + + /// + public override async Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (AuthenticationSchemes == null) + { + throw new InvalidOperationException( + Resources.FormatPropertyOfTypeCannotBeNull( + /* property: */ nameof(AuthenticationSchemes), + /* type: */ nameof(SignOutResult))); + } + + var loggerFactory = context.HttpContext.RequestServices.GetRequiredService(); + var logger = loggerFactory.CreateLogger(); + + logger.SignOutResultExecuting(AuthenticationSchemes); + + if (AuthenticationSchemes.Count == 0) + { + await context.HttpContext.SignOutAsync(Properties); + } + else + { + for (var i = 0; i < AuthenticationSchemes.Count; i++) + { + await context.HttpContext.SignOutAsync(AuthenticationSchemes[i], Properties); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs new file mode 100644 index 0000000000..8db690c94e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.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 Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Represents an that when executed will + /// produce an HTTP response with the given response status code. + /// + public class StatusCodeResult : ActionResult + { + /// + /// Initializes a new instance of the class + /// with the given . + /// + /// The HTTP status code of the response. + public StatusCodeResult(int statusCode) + { + StatusCode = statusCode; + } + + /// + /// Gets the HTTP status code. + /// + public int StatusCode { get; } + + /// + public override void ExecuteResult(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var factory = context.HttpContext.RequestServices.GetRequiredService(); + var logger = factory.CreateLogger(); + + logger.HttpStatusCodeResultExecuting(StatusCode); + + context.HttpContext.Response.StatusCode = StatusCode; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/TypeFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/TypeFilterAttribute.cs new file mode 100644 index 0000000000..bc5c19802f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/TypeFilterAttribute.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.Diagnostics; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A filter that creates another filter of type , retrieving missing constructor + /// arguments from dependency injection if available there. + /// + /// + /// + /// Primarily used in calls. + /// + /// + /// Similar to the in that both use constructor injection. Use + /// instead if the filter is itself a service. + /// + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] + [DebuggerDisplay("TypeFilter: Type={ImplementationType} Order={Order}")] + public class TypeFilterAttribute : Attribute, IFilterFactory, IOrderedFilter + { + private ObjectFactory _factory; + + /// + /// Instantiates a new instance. + /// + /// The of filter to create. + public TypeFilterAttribute(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + ImplementationType = type; + } + + /// + /// Gets or sets the non-service arguments to pass to the constructor. + /// + /// + /// Service arguments are found in the dependency injection container i.e. this filter supports constructor + /// injection in addition to passing the given . + /// + public object[] Arguments { get; set; } + + /// + /// Gets the of filter to create. + /// + public Type ImplementationType { get; } + + /// + public int Order { get; set; } + + /// + public bool IsReusable { get; set; } + + /// + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + if (_factory == null) + { + var argumentTypes = Arguments?.Select(a => a.GetType())?.ToArray(); + + _factory = ActivatorUtilities.CreateFactory(ImplementationType, argumentTypes ?? Type.EmptyTypes); + } + + return (IFilterMetadata)_factory(serviceProvider, Arguments); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedResult.cs new file mode 100644 index 0000000000..a92106ec84 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedResult.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 +{ + /// + /// Represents an that when + /// executed will produce an Unauthorized (401) response. + /// + public class UnauthorizedResult : StatusCodeResult + { + /// + /// Creates a new instance. + /// + public UnauthorizedResult() : base(StatusCodes.Status401Unauthorized) + { + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs new file mode 100644 index 0000000000..002d9d97af --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.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.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that when executed will produce a Unprocessable Entity (422) response. + /// + public class UnprocessableEntityObjectResult : ObjectResult + { + /// + /// Creates a new instance. + /// + /// containing the validation errors. + public UnprocessableEntityObjectResult(ModelStateDictionary modelState) + : this(new SerializableError(modelState)) + { + } + + /// + /// Creates a new instance. + /// + /// Contains errors to be returned to the client. + public UnprocessableEntityObjectResult(object error) + : base(error) + { + StatusCode = StatusCodes.Status422UnprocessableEntity; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityResult.cs new file mode 100644 index 0000000000..0851057499 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityResult.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 Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A that when + /// executed will produce a Unprocessable Entity (422) response. + /// + public class UnprocessableEntityResult : StatusCodeResult + { + /// + /// Creates a new instance. + /// + public UnprocessableEntityResult() + : base(StatusCodes.Status422UnprocessableEntity) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnsupportedMediaTypeResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnsupportedMediaTypeResult.cs new file mode 100644 index 0000000000..445ded67d0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnsupportedMediaTypeResult.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 +{ + /// + /// A that when + /// executed will produce a UnsupportedMediaType (415) response. + /// + public class UnsupportedMediaTypeResult : StatusCodeResult + { + /// + /// Creates a new instance of . + /// + public UnsupportedMediaTypeResult() : base(StatusCodes.Status415UnsupportedMediaType) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs new file mode 100644 index 0000000000..93720a5826 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs @@ -0,0 +1,501 @@ +// Copyright (c) .NET 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.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + public static class UrlHelperExtensions + { + /// + /// Generates a URL with an absolute path for an action method. + /// + /// The . + /// The generated URL. + public static string Action(this IUrlHelper helper) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.Action( + action: null, + controller: null, + values: null, + protocol: null, + host: null, + fragment: null); + } + + /// + /// Generates a URL with an absolute path for an action method, which contains the specified + /// name. + /// + /// The . + /// The name of the action method. + /// The generated URL. + public static string Action(this IUrlHelper helper, string action) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.Action(action, controller: null, values: null, protocol: null, host: null, fragment: null); + } + + /// + /// Generates a URL with an absolute path for an action method, which contains the specified + /// name and route . + /// + /// The . + /// The name of the action method. + /// An object that contains route values. + /// The generated URL. + public static string Action(this IUrlHelper helper, string action, object values) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.Action(action, controller: null, values: values, protocol: null, host: null, fragment: null); + } + + /// + /// Generates a URL with an absolute path for an action method, which contains the specified + /// and names. + /// + /// The . + /// The name of the action method. + /// The name of the controller. + /// The generated URL. + public static string Action(this IUrlHelper helper, string action, string controller) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.Action(action, controller, values: null, protocol: null, host: null, fragment: null); + } + + /// + /// Generates a URL with an absolute path for an action method, which contains the specified + /// name, name, and route . + /// + /// The . + /// The name of the action method. + /// The name of the controller. + /// An object that contains route values. + /// The generated URL. + public static string Action(this IUrlHelper helper, string action, string controller, object values) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.Action(action, controller, values, protocol: null, host: null, fragment: null); + } + + /// + /// Generates a URL with an absolute path for an action method, which contains the specified + /// name, name, route , and + /// to use. + /// + /// The . + /// The name of the action method. + /// The name of the controller. + /// An object that contains route values. + /// The protocol for the URL, such as "http" or "https". + /// The generated URL. + public static string Action( + this IUrlHelper helper, + string action, + string controller, + object values, + string protocol) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.Action(action, controller, values, protocol, host: null, fragment: null); + } + + /// + /// Generates a URL with an absolute path for an action method, which contains the specified + /// name, name, route , + /// to use, and name. + /// Generates an absolute URL if the and are + /// non-null. + /// + /// The . + /// The name of the action method. + /// The name of the controller. + /// An object that contains route values. + /// The protocol for the URL, such as "http" or "https". + /// The host name for the URL. + /// The generated URL. + public static string Action( + this IUrlHelper helper, + string action, + string controller, + object values, + string protocol, + string host) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.Action(action, controller, values, protocol, host, fragment: null); + } + + /// + /// Generates a URL with an absolute path for an action method, which contains the specified + /// name, name, route , + /// to use, name, and . + /// Generates an absolute URL if the and are + /// non-null. + /// + /// The . + /// The name of the action method. + /// The name of the controller. + /// An object that contains route values. + /// The protocol for the URL, such as "http" or "https". + /// The host name for the URL. + /// The fragment for the URL. + /// The generated URL. + public static string Action( + this IUrlHelper helper, + string action, + string controller, + object values, + string protocol, + string host, + string fragment) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.Action(new UrlActionContext() + { + Action = action, + Controller = controller, + Host = host, + Values = values, + Protocol = protocol, + Fragment = fragment + }); + } + + /// + /// Generates a URL with an absolute path for the specified route . + /// + /// The . + /// An object that contains route values. + /// The generated URL. + public static string RouteUrl(this IUrlHelper helper, object values) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.RouteUrl(routeName: null, values: values, protocol: null, host: null, fragment: null); + } + + /// + /// Generates a URL with an absolute path for the specified . + /// + /// The . + /// The name of the route that is used to generate URL. + /// The generated URL. + public static string RouteUrl(this IUrlHelper helper, string routeName) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.RouteUrl(routeName, values: null, protocol: null, host: null, fragment: null); + } + + /// + /// Generates a URL with an absolute path for the specified and route + /// . + /// + /// The . + /// The name of the route that is used to generate URL. + /// An object that contains route values. + /// The generated URL. + public static string RouteUrl(this IUrlHelper helper, string routeName, object values) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.RouteUrl(routeName, values, protocol: null, host: null, fragment: null); + } + + /// + /// Generates a URL with an absolute path for the specified route and route + /// , which contains the specified to use. + /// + /// 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. + public static string RouteUrl( + this IUrlHelper helper, + string routeName, + object values, + string protocol) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.RouteUrl(routeName, values, protocol, host: null, fragment: null); + } + + /// + /// Generates a URL with an absolute path for the specified route and route + /// , which contains the specified to use and + /// name. Generates an absolute URL if + /// and are non-null. + /// + /// 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 host name for the URL. + /// The generated URL. + public static string RouteUrl( + this IUrlHelper helper, + string routeName, + object values, + string protocol, + string host) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.RouteUrl(routeName, values, protocol, host, fragment: null); + } + + /// + /// Generates a URL with an absolute path for the specified route and route + /// , which contains the specified to use, + /// name and . Generates an absolute URL if + /// and are non-null. + /// + /// 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 host name for the URL. + /// The fragment for the URL. + /// The generated URL. + public static string RouteUrl( + this IUrlHelper helper, + string routeName, + object values, + string protocol, + string host, + string fragment) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + return helper.RouteUrl(new UrlRouteContext() + { + RouteName = routeName, + Values = values, + Protocol = protocol, + Host = host, + Fragment = fragment + }); + } + + /// + /// Generates a URL with an absolute path for the specified . + /// + /// The . + /// The page name to generate the url for. + /// The generated URL. + public static string Page(this IUrlHelper urlHelper, string pageName) + => Page(urlHelper, pageName, values: null); + + /// + /// Generates a URL with an absolute path for the specified . + /// + /// The . + /// The page name to generate the url for. + /// The handler to generate the url for. + /// The generated URL. + public static string Page(this IUrlHelper urlHelper, string pageName, string pageHandler) + => Page(urlHelper, pageName, pageHandler, values: null); + + /// + /// Generates a URL with an absolute path for the specified . + /// + /// The . + /// The page name to generate the url for. + /// An object that contains route values. + /// The generated URL. + public static string Page(this IUrlHelper urlHelper, string pageName, object values) + => Page(urlHelper, pageName, pageHandler: null, values: values); + + /// + /// Generates a URL with an absolute path for the specified . + /// + /// The . + /// The page name to generate the url for. + /// The handler to generate the url for. + /// An object that contains route values. + /// The generated URL. + public static string Page( + this IUrlHelper urlHelper, + string pageName, + string pageHandler, + object values) + => Page(urlHelper, pageName, pageHandler, values, protocol: null); + + /// + /// Generates a URL with an absolute path for the specified . + /// + /// The . + /// The page name to generate the url for. + /// The handler to generate the url for. + /// An object that contains route values. + /// The protocol for the URL, such as "http" or "https". + /// The generated URL. + public static string Page( + this IUrlHelper urlHelper, + string pageName, + string pageHandler, + object values, + string protocol) + => Page(urlHelper, pageName, pageHandler, values, protocol, host: null, fragment: null); + + /// + /// Generates a URL with an absolute path for the specified . + /// + /// The . + /// The page name to generate the url for. + /// The handler to generate the url for. + /// An object that contains route values. + /// The protocol for the URL, such as "http" or "https". + /// The host name for the URL. + /// The generated URL. + public static string Page( + this IUrlHelper urlHelper, + string pageName, + string pageHandler, + object values, + string protocol, + string host) + => Page(urlHelper, pageName, pageHandler, values, protocol, host, fragment: null); + + /// + /// Generates a URL with an absolute path for the specified . + /// + /// The . + /// The page name to generate the url for. + /// The handler to generate the url for. + /// An object that contains route values. + /// The protocol for the URL, such as "http" or "https". + /// The host name for the URL. + /// The fragment for the URL. + /// The generated URL. + public static string Page( + this IUrlHelper urlHelper, + string pageName, + string pageHandler, + object values, + string protocol, + string host, + string fragment) + { + if (urlHelper == null) + { + throw new ArgumentNullException(nameof(urlHelper)); + } + + 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 formaction unless it's explicitly specified in the routeValues. + routeValues["handler"] = null; + } + } + else + { + routeValues["handler"] = pageHandler; + } + + return urlHelper.RouteUrl( + routeName: null, + values: routeValues, + protocol: protocol, + 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/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs new file mode 100644 index 0000000000..b27e790a90 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.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; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A for validation errors. + /// + public class ValidationProblemDetails : ProblemDetails + { + /// + /// Intializes a new instance of . + /// + public ValidationProblemDetails() + { + Title = Resources.ValidationProblemDescription_Title; + } + + public ValidationProblemDetails(ModelStateDictionary modelState) + : this() + { + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + foreach (var keyModelStatePair in modelState) + { + var key = keyModelStatePair.Key; + var errors = keyModelStatePair.Value.Errors; + if (errors != null && errors.Count > 0) + { + if (errors.Count == 1) + { + var errorMessage = GetErrorMessage(errors[0]); + Errors.Add(key, new[] { errorMessage }); + } + else + { + var errorMessages = new string[errors.Count]; + for (var i = 0; i < errors.Count; i++) + { + errorMessages[i] = GetErrorMessage(errors[i]); + } + + Errors.Add(key, errorMessages); + } + } + } + + string GetErrorMessage(ModelError error) + { + return string.IsNullOrEmpty(error.ErrorMessage) ? + Resources.SerializableError_DefaultError : + error.ErrorMessage; + } + } + + /// + /// Gets or sets the validation errors associated with this instance of . + /// + public IDictionary Errors { get; } = new Dictionary(StringComparer.Ordinal); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ViewFeatures/IKeepTempDataResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ViewFeatures/IKeepTempDataResult.cs new file mode 100644 index 0000000000..5d86fc171e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ViewFeatures/IKeepTempDataResult.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.ViewFeatures +{ + /// + /// A marker interface for types which need to have temp data saved. + /// + public interface IKeepTempDataResult : IActionResult + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/VirtualFileResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/VirtualFileResult.cs new file mode 100644 index 0000000000..2e37c94a89 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/VirtualFileResult.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; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A that on execution writes the file specified using a virtual path to the response + /// using mechanisms provided by the host. + /// + public class VirtualFileResult : FileResult + { + private string _fileName; + + /// + /// Creates a new instance with the provided + /// and the provided . + /// + /// The path to the file. The path must be relative/virtual. + /// The Content-Type header of the response. + public VirtualFileResult(string fileName, string contentType) + : this(fileName, MediaTypeHeaderValue.Parse(contentType)) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + } + + /// + /// Creates a new instance with + /// the provided and the + /// provided . + /// + /// The path to the file. The path must be relative/virtual. + /// The Content-Type header of the response. + public VirtualFileResult(string fileName, MediaTypeHeaderValue contentType) + : base(contentType?.ToString()) + { + if (fileName == null) + { + throw new ArgumentNullException(nameof(fileName)); + } + + FileName = fileName; + } + + /// + /// Gets or sets the path to the file that will be sent back as the response. + /// + public string FileName + { + get + { + return _fileName; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _fileName = value; + } + } + + /// + /// Gets or sets the used to resolve paths. + /// + public IFileProvider FileProvider { get; set; } + + /// + public override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var executor = context.HttpContext.RequestServices.GetRequiredService>(); + return executor.ExecuteAsync(context, this); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/baseline.netcore.json new file mode 100644 index 0000000000..b0e0b7509f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/baseline.netcore.json @@ -0,0 +1,26187 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Core, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.Builder.MvcApplicationBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "UseMvc", + "Parameters": [ + { + "Name": "app", + "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" + } + ], + "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UseMvcWithDefaultRoute", + "Parameters": [ + { + "Name": "app", + "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" + } + ], + "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UseMvc", + "Parameters": [ + { + "Name": "app", + "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" + }, + { + "Name": "configureRoutes", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Builder.MvcAreaRouteBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "MapAreaRoute", + "Parameters": [ + { + "Name": "routeBuilder", + "Type": "Microsoft.AspNetCore.Routing.IRouteBuilder" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "template", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Routing.IRouteBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MapAreaRoute", + "Parameters": [ + { + "Name": "routeBuilder", + "Type": "Microsoft.AspNetCore.Routing.IRouteBuilder" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "template", + "Type": "System.String" + }, + { + "Name": "defaults", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Routing.IRouteBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MapAreaRoute", + "Parameters": [ + { + "Name": "routeBuilder", + "Type": "Microsoft.AspNetCore.Routing.IRouteBuilder" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "template", + "Type": "System.String" + }, + { + "Name": "defaults", + "Type": "System.Object" + }, + { + "Name": "constraints", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Routing.IRouteBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MapAreaRoute", + "Parameters": [ + { + "Name": "routeBuilder", + "Type": "Microsoft.AspNetCore.Routing.IRouteBuilder" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "template", + "Type": "System.String" + }, + { + "Name": "defaults", + "Type": "System.Object" + }, + { + "Name": "constraints", + "Type": "System.Object" + }, + { + "Name": "dataTokens", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Routing.IRouteBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.AcceptedAtActionResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_UrlHelper", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_UrlHelper", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ActionName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ControllerName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ControllerName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteValues", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnFormatting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.AcceptedAtRouteResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_UrlHelper", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_UrlHelper", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteValues", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnFormatting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.AcceptedResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Location", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Location", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnFormatting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "location", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "locationUri", + "Type": "System.Uri" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.AcceptVerbsAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Routing.IActionHttpMethodProvider", + "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_HttpMethods", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Routing.IActionHttpMethodProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Route", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Route", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "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": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "method", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "methods", + "Type": "System.String[]", + "IsParams": true + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionContextAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionNameAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionResult", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.IActionResult" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupNameProvider", + "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionVisibilityProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_GroupName", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupNameProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_GroupName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IgnoreApi", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionVisibilityProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IgnoreApi", + "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.AreaAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Routing.RouteValueAttribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "areaName", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.BadRequestObjectResult", + "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.BadRequestResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.BindAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider", + "Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Include", + "Parameters": [], + "ReturnType": "System.String[]", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Prefix", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Prefix", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyFilter", + "Parameters": [], + "ReturnType": "System.Func", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "include", + "Type": "System.String[]", + "IsParams": true + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider", + "Microsoft.AspNetCore.Mvc.ModelBinding.IBinderTypeProviderMetadata", + "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", + "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": "Method", + "Name": "get_BinderType", + "Parameters": [], + "ReturnType": "System.Type", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBinderTypeProviderMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BinderType", + "Parameters": [ + { + "Name": "value", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BindingSource", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "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.CacheProfile", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Duration", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Duration", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Location", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Location", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_NoStore", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_NoStore", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "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": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AuthenticationSchemes", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationProperties", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Properties", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationScheme", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationScheme", + "Type": "System.String" + }, + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", + "Microsoft.AspNetCore.Mvc.Internal.IConsumesActionConstraint", + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ContentTypes", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" + } + ], + "ReturnType": "System.Void", + "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": "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": "SetContentTypes", + "Parameters": [ + { + "Name": "contentTypes", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiRequestMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "otherContentTypes", + "Type": "System.String[]", + "IsParams": true + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "ConsumesActionConstraintOrder", + "Parameters": [], + "ReturnType": "System.Int32", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ContentResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Content", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContentType", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ContentType", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_StatusCode", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_StatusCode", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ControllerAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ControllerBase", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_HttpContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Request", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpRequest", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Response", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpResponse", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteData", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteData", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ControllerContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ControllerContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ControllerContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MetadataProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MetadataProvider", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelBinderFactory", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelBinderFactory", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Url", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Url", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ObjectValidator", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ObjectValidator", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_User", + "Parameters": [], + "ReturnType": "System.Security.Claims.ClaimsPrincipal", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "StatusCode", + "Parameters": [ + { + "Name": "statusCode", + "Type": "System.Int32" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "StatusCode", + "Parameters": [ + { + "Name": "statusCode", + "Type": "System.Int32" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "contentEncoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "NoContent", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.NoContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Ok", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.OkResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Ok", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.OkObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Redirect", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectPermanent", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectPreserveMethod", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectPermanentPreserveMethod", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirect", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirectPermanent", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirectPreserveMethod", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirectPermanentPreserveMethod", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPreserveMethod", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "controllerName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanentPreserveMethod", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "controllerName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePreserveMethod", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanentPreserveMethod", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePreserveMethod", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanentPreserveMethod", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.Nullable" + }, + { + "Name": "entityTag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "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", + "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" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "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", + "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" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "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", + "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" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "virtualPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "virtualPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "virtualPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.Nullable" + }, + { + "Name": "entityTag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "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", + "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" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "physicalPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "physicalPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "physicalPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.Nullable" + }, + { + "Name": "entityTag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "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", + "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" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.UnauthorizedResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "NotFound", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.NotFoundResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "NotFound", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.NotFoundObjectResult", + "Virtual": true, + "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": "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", + "Parameters": [ + { + "Name": "uri", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.CreatedResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Created", + "Parameters": [ + { + "Name": "uri", + "Type": "System.Uri" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.CreatedResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreatedAtAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.CreatedAtActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreatedAtAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.CreatedAtActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreatedAtAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.CreatedAtActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreatedAtRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.CreatedAtRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreatedAtRoute", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.CreatedAtRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreatedAtRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.CreatedAtRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Accepted", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Accepted", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Accepted", + "Parameters": [ + { + "Name": "uri", + "Type": "System.Uri" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Accepted", + "Parameters": [ + { + "Name": "uri", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Accepted", + "Parameters": [ + { + "Name": "uri", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Accepted", + "Parameters": [ + { + "Name": "uri", + "Type": "System.Uri" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AcceptedAtAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedAtActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AcceptedAtAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedAtActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AcceptedAtAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedAtActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AcceptedAtAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedAtActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AcceptedAtAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedAtActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AcceptedAtAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedAtActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AcceptedAtRoute", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedAtRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AcceptedAtRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedAtRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AcceptedAtRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedAtRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AcceptedAtRoute", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedAtRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AcceptedAtRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.AcceptedAtRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Challenge", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Challenge", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Challenge", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Challenge", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignIn", + "Parameters": [ + { + "Name": "principal", + "Type": "System.Security.Claims.ClaimsPrincipal" + }, + { + "Name": "authenticationScheme", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignInResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignIn", + "Parameters": [ + { + "Name": "principal", + "Type": "System.Security.Claims.ClaimsPrincipal" + }, + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationScheme", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignInResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignOut", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignOutResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignOut", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignOutResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "model", + "Type": "T0" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": 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" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": 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": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": 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": "includeExpressions", + "Type": "System.Linq.Expressions.Expression>[]", + "IsParams": true + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "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": "propertyFilter", + "Type": "System.Func" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "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": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "includeExpressions", + "Type": "System.Linq.Expressions.Expression>[]", + "IsParams": true + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "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": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "propertyFilter", + "Type": "System.Func" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "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" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": 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": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "propertyFilter", + "Type": "System.Func" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryValidateModel", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryValidateModel", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + }, + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ControllerContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ActionContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionDescriptor", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionDescriptor", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueProviderFactories", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValueProviderFactories", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ControllerContextAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.CreatedAtActionResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_UrlHelper", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_UrlHelper", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ActionName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ControllerName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ControllerName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteValues", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnFormatting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.CreatedAtRouteResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_UrlHelper", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_UrlHelper", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteValues", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnFormatting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.CreatedResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Location", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Location", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnFormatting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "location", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "location", + "Type": "System.Uri" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.DisableRequestSizeLimitAttribute", + "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": "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.EmptyResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.Byte[]", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_FileContents", + "Parameters": [ + { + "Name": "value", + "Type": "System.Byte[]" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.FileResult", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ContentType", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_FileDownloadName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_FileDownloadName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_LastModified", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_LastModified", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_EntityTag", + "Parameters": [], + "ReturnType": "Microsoft.Net.Http.Headers.EntityTagHeaderValue", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_EntityTag", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + } + ], + "ReturnType": "System.Void", + "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", + "Parameters": [ + { + "Name": "contentType", + "Type": "System.String" + } + ], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.IO.Stream", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_FileStream", + "Parameters": [ + { + "Name": "value", + "Type": "System.IO.Stream" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.MiddlewareFilterAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", + "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ConfigurationType", + "Parameters": [], + "ReturnType": "System.Type", + "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": "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": "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": "configurationType", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AuthenticationSchemes", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationProperties", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Properties", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationScheme", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationScheme", + "Type": "System.String" + }, + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.FormatFilterAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory" + ], + "Members": [ + { + "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.FromBodyAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.FromFormAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "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.FromHeaderAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "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.FromQueryAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "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.FromRouteAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "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.FromServicesAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.HttpDeleteAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "template", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.HttpGetAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "template", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.HttpHeadAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "template", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.HttpOptionsAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "template", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.HttpPatchAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "template", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.HttpPostAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "template", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.HttpPutAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "template", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.IDesignTimeMvcBuilderConfiguration", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "ConfigureMvc", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "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", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Permanent", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PreserveMethod", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PreserveMethod", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Url", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Url", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_UrlHelper", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_UrlHelper", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + }, + { + "Name": "preserveMethod", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinderAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider", + "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", + "Parameters": [], + "ReturnType": "System.Type", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBinderTypeProviderMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BinderType", + "Parameters": [ + { + "Name": "value", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BindingSource", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "binderType", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelMetadataTypeAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_MetadataType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.MvcOptions", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_AllowEmptyInputInBodyModelBinding", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AllowEmptyInputInBodyModelBinding", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "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", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Conventions", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Filters", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.FilterCollection", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_FormatterMappings", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.FormatterMappings", + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.FormatterCollection", + "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", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SuppressInputFormatterBuffering", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MaxModelValidationErrors", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MaxModelValidationErrors", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelBinderProviders", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelBindingMessageProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelBindingMessageProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelMetadataDetailsProviders", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelValidatorProviders", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_OutputFormatters", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.FormatterCollection", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RespectBrowserAcceptHeader", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RespectBrowserAcceptHeader", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ReturnHttpNotAcceptable", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ReturnHttpNotAcceptable", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueProviderFactories", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SslPort", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SslPort", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RequireHttpsPermanent", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RequireHttpsPermanent", + "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.NoContentResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.NonActionAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.NonControllerAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.NonViewComponentAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.NotFoundObjectResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.NotFoundResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ObjectResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Value", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Formatters", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.FormatterCollection", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Formatters", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.FormatterCollection" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContentTypes", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ContentTypes", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DeclaredType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DeclaredType", + "Parameters": [ + { + "Name": "value", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_StatusCode", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_StatusCode", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnFormatting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.OkObjectResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.OkResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_FileName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "fileName", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "fileName", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IResultFilter", + "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter", + "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseMetadataProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Type", + "Parameters": [], + "ReturnType": "System.Type", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Type", + "Parameters": [ + { + "Name": "value", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContentTypes", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ContentTypes", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_StatusCode", + "Parameters": [], + "ReturnType": "System.Int32", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseMetadataProvider", + "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": "set_Order", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnResultExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext" + } + ], + "ReturnType": "System.Void", + "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", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IResultFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetContentTypes", + "Parameters": [ + { + "Name": "contentTypes", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "additionalContentTypes", + "Type": "System.String[]", + "IsParams": true + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseMetadataProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Type", + "Parameters": [], + "ReturnType": "System.Type", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseMetadataProvider", + "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", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_StatusCode", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "statusCode", + "Type": "System.Int32" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + }, + { + "Name": "statusCode", + "Type": "System.Int32" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", + "ImplementedInterfaces": [ + "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", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Permanent", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PreserveMethod", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PreserveMethod", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Url", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Url", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_UrlHelper", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_UrlHelper", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + }, + { + "Name": "preserveMethod", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", + "ImplementedInterfaces": [ + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_UrlHelper", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ActionName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ControllerName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ControllerName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteValues", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Permanent", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Permanent", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PreserveMethod", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PreserveMethod", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Fragment", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Fragment", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + }, + { + "Name": "preserveMethod", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + }, + { + "Name": "preserveMethod", + "Type": "System.Boolean" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", + "ImplementedInterfaces": [ + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_UrlHelper", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PageName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PageName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PageHandler", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PageHandler", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteValues", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Permanent", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Permanent", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PreserveMethod", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PreserveMethod", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Fragment", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Fragment", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Protocol", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Protocol", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Host", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Host", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + }, + { + "Name": "preserveMethod", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + }, + { + "Name": "preserveMethod", + "Type": "System.Boolean" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", + "ImplementedInterfaces": [ + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_UrlHelper", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteValues", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Permanent", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Permanent", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PreserveMethod", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PreserveMethod", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Fragment", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Fragment", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + }, + { + "Name": "preserveMethod", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "permanent", + "Type": "System.Boolean" + }, + { + "Name": "preserveMethod", + "Type": "System.Boolean" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "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": "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": "bytes", + "Type": "System.Int64" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RequireHttpsAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IAuthorizationFilter", + "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Permanent", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Permanent", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "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": "set_Order", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnAuthorization", + "Parameters": [ + { + "Name": "filterContext", + "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAuthorizationFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "HandleNonHttpsRequest", + "Parameters": [ + { + "Name": "filterContext", + "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ResponseCacheAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", + "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter" + ], + "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": "get_CacheProfileName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_CacheProfileName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "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": "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": "GetCacheProfile", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.CacheProfile", + "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.ResponseCacheLocation", + "Visibility": "Public", + "Kind": "Enumeration", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Field", + "Name": "Any", + "Parameters": [], + "GenericParameter": [], + "Literal": "0" + }, + { + "Kind": "Field", + "Name": "Client", + "Parameters": [], + "GenericParameter": [], + "Literal": "1" + }, + { + "Kind": "Field", + "Name": "None", + "Parameters": [], + "GenericParameter": [], + "Literal": "2" + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RouteAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Template", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "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": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "template", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.SerializableError", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Collections.Generic.Dictionary", + "ImplementedInterfaces": [], + "Members": [ + { + "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.ServiceFilterAttribute", + "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_ServiceType", + "Parameters": [], + "ReturnType": "System.Type", + "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": "set_IsReusable", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "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": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.SignInResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AuthenticationScheme", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Principal", + "Parameters": [], + "ReturnType": "System.Security.Claims.ClaimsPrincipal", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Principal", + "Parameters": [ + { + "Name": "value", + "Type": "System.Security.Claims.ClaimsPrincipal" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationProperties", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Properties", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationScheme", + "Type": "System.String" + }, + { + "Name": "principal", + "Type": "System.Security.Claims.ClaimsPrincipal" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationScheme", + "Type": "System.String" + }, + { + "Name": "principal", + "Type": "System.Security.Claims.ClaimsPrincipal" + }, + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.SignOutResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AuthenticationSchemes", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Authentication.AuthenticationProperties", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Properties", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationScheme", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationScheme", + "Type": "System.String" + }, + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_StatusCode", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "statusCode", + "Type": "System.Int32" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.TypeFilterAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", + "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Arguments", + "Parameters": [], + "ReturnType": "System.Object[]", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Arguments", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object[]" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ImplementationType", + "Parameters": [], + "ReturnType": "System.Type", + "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": "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": "set_IsReusable", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "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": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.UnauthorizedResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.UrlHelperExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Action", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Action", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "action", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Action", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "action", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Action", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "action", + "Type": "System.String" + }, + { + "Name": "controller", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Action", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "action", + "Type": "System.String" + }, + { + "Name": "controller", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Action", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "action", + "Type": "System.String" + }, + { + "Name": "controller", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + }, + { + "Name": "protocol", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Action", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "action", + "Type": "System.String" + }, + { + "Name": "controller", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + }, + { + "Name": "protocol", + "Type": "System.String" + }, + { + "Name": "host", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Action", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "action", + "Type": "System.String" + }, + { + "Name": "controller", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + }, + { + "Name": "protocol", + "Type": "System.String" + }, + { + "Name": "host", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RouteUrl", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "values", + "Type": "System.Object" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RouteUrl", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "routeName", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RouteUrl", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RouteUrl", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + }, + { + "Name": "protocol", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RouteUrl", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + }, + { + "Name": "protocol", + "Type": "System.String" + }, + { + "Name": "host", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RouteUrl", + "Parameters": [ + { + "Name": "helper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + }, + { + "Name": "protocol", + "Type": "System.String" + }, + { + "Name": "host", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Page", + "Parameters": [ + { + "Name": "urlHelper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Page", + "Parameters": [ + { + "Name": "urlHelper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Page", + "Parameters": [ + { + "Name": "urlHelper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Page", + "Parameters": [ + { + "Name": "urlHelper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Page", + "Parameters": [ + { + "Name": "urlHelper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + }, + { + "Name": "protocol", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Page", + "Parameters": [ + { + "Name": "urlHelper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + }, + { + "Name": "protocol", + "Type": "System.String" + }, + { + "Name": "host", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Page", + "Parameters": [ + { + "Name": "urlHelper", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + }, + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + }, + { + "Name": "protocol", + "Type": "System.String" + }, + { + "Name": "host", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_FileName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_FileProvider", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_FileProvider", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.Extensions.FileProviders.IFileProvider" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "fileName", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "fileName", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.IKeepTempDataResult", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.IActionResult" + ], + "Members": [], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Routing.HttpMethodAttribute", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Routing.IActionHttpMethodProvider", + "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_HttpMethods", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Routing.IActionHttpMethodProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Template", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "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": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "httpMethods", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "httpMethods", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "template", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Routing.IActionHttpMethodProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_HttpMethods", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Template", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Nullable", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Routing.IRouteValueProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_RouteKey", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValue", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetUrlHelper", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Routing.KnownRouteValueConstraint", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Routing.IRouteConstraint" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Match", + "Parameters": [ + { + "Name": "httpContext", + "Type": "Microsoft.AspNetCore.Http.HttpContext" + }, + { + "Name": "route", + "Type": "Microsoft.AspNetCore.Routing.IRouter" + }, + { + "Name": "routeKey", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary" + }, + { + "Name": "routeDirection", + "Type": "Microsoft.AspNetCore.Routing.RouteDirection" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Routing.IRouteConstraint", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Routing.RouteValueAttribute", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Routing.IRouteValueProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_RouteKey", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Routing.IRouteValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValue", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Routing.IRouteValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "routeKey", + "Type": "System.String" + }, + { + "Name": "routeValue", + "Type": "System.String" + } + ], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Routing.UrlHelper", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.IUrlHelper" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AmbientValues", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteValueDictionary", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HttpContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpContext", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Router", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.IRouter", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Action", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.Routing.UrlActionContext" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IsLocalUrl", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RouteUrl", + "Parameters": [ + { + "Name": "routeContext", + "Type": "Microsoft.AspNetCore.Mvc.Routing.UrlRouteContext" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetVirtualPathData", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary" + } + ], + "ReturnType": "Microsoft.AspNetCore.Routing.VirtualPathData", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "contentPath", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Link", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GenerateUrl", + "Parameters": [ + { + "Name": "protocol", + "Type": "System.String" + }, + { + "Name": "host", + "Type": "System.String" + }, + { + "Name": "pathData", + "Type": "Microsoft.AspNetCore.Routing.VirtualPathData" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Routing.UrlHelperFactory", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetUrlHelper", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.DefaultModelBindingContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_FieldName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_FieldName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Model", + "Parameters": [], + "ReturnType": "System.Object", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Model", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelMetadata", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelState", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BinderModelName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BinderModelName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BindingSource", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsTopLevelObject", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsTopLevelObject", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_OriginalValueProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_OriginalValueProvider", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValueProvider", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyFilter", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PropertyFilter", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidationState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValidationState", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateBindingContext", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "bindingInfo", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo" + }, + { + "Name": "modelName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EnterNestedScope", + "Parameters": [ + { + "Name": "modelMetadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "fieldName", + "Type": "System.String" + }, + { + "Name": "modelName", + "Type": "System.String" + }, + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext+NestedScope", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EnterNestedScope", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext+NestedScope", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExitNestedScope", + "Parameters": [], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingBehavior", + "Visibility": "Public", + "Kind": "Enumeration", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Field", + "Name": "Optional", + "Parameters": [], + "GenericParameter": [], + "Literal": "0" + }, + { + "Kind": "Field", + "Name": "Never", + "Parameters": [], + "GenericParameter": [], + "Literal": "1" + }, + { + "Kind": "Field", + "Name": "Required", + "Parameters": [], + "GenericParameter": [], + "Literal": "2" + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingBehaviorAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Behavior", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingBehavior", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "behavior", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingBehavior" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSourceValueProvider", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceValueProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ContainsPrefix", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": 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, + "Abstract": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Filter", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.BindNeverAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingBehaviorAttribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.BindRequiredAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingBehaviorAttribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.CompositeValueProvider", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Collections.ObjectModel.Collection", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", + "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", + "Parameters": [ + { + "Name": "controllerContext", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateAsync", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "factories", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Static": true, + "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": "InsertItem", + "Parameters": [ + { + "Name": "index", + "Type": "System.Int32" + }, + { + "Name": "item", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetItem", + "Parameters": [ + { + "Name": "index", + "Type": "System.Int32" + }, + { + "Name": "item", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Filter", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceValueProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "valueProviders", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.DefaultPropertyFilterProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Prefix", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyIncludeExpressions", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable>>", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyFilter", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.EmptyModelMetadataProvider", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadataProvider", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.FormValueProvider", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSourceValueProvider", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider" + ], + "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", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + }, + { + "Name": "values", + "Type": "Microsoft.AspNetCore.Http.IFormCollection" + }, + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.FormValueProviderFactory", + "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.IBindingSourceValueProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Filter", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ICollectionModelBinder", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CanCreateInstance", + "Parameters": [ + { + "Name": "targetType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Boolean", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetKeysFromPrefix", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "System.Collections.Generic.IDictionary", + "GenericParameter": [] + } + ], + "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", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateBinder", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderFactoryContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.JQueryFormValueProvider", + "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.JQueryFormValueProviderFactory", + "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.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", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Attributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ParameterAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_TypeAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAttributesForProperty", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + }, + { + "Name": "property", + "Type": "System.Reflection.PropertyInfo" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelAttributes", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAttributesForType", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelAttributes", + "Static": true, + "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", + "Parameters": [ + { + "Name": "typeAttributes", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "propertyAttributes", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "typeAttributes", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderFactory", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CreateBinder", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderFactoryContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "metadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "options", + "Type": "Microsoft.Extensions.Options.IOptions" + } + ], + "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderFactoryContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_BindingInfo", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BindingInfo", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Metadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Metadata", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_CacheToken", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_CacheToken", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderProviderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "list", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TModelBinderProvider", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "list", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadataProviderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetMetadataForProperty", + "Parameters": [ + { + "Name": "provider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "containerType", + "Type": "System.Type" + }, + { + "Name": "propertyName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelNames", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateIndexModelName", + "Parameters": [ + { + "Name": "parentName", + "Type": "System.String" + }, + { + "Name": "index", + "Type": "System.Int32" + } + ], + "ReturnType": "System.String", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateIndexModelName", + "Parameters": [ + { + "Name": "parentName", + "Type": "System.String" + }, + { + "Name": "index", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreatePropertyModelName", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + }, + { + "Name": "propertyName", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "parameter", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BindModelAsync", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "parameter", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BindModelAsync", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "modelBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + }, + { + "Name": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "parameter", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor" + }, + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "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" + } + ], + "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.QueryStringValueProvider", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSourceValueProvider", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider" + ], + "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", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + }, + { + "Name": "values", + "Type": "Microsoft.AspNetCore.Http.IQueryCollection" + }, + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.QueryStringValueProviderFactory", + "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.RouteValueProvider", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSourceValueProvider", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "ContainsPrefix", + "Parameters": [ + { + "Name": "key", + "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_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", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + }, + { + "Name": "values", + "Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + }, + { + "Name": "values", + "Type": "Microsoft.AspNetCore.Routing.RouteValueDictionary" + }, + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.RouteValueProviderFactory", + "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.SuppressChildValidationMetadataProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IValidationMetadataProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Type", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_FullTypeName", + "Parameters": [], + "ReturnType": "System.String", + "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": "type", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "fullTypeName", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeException", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Exception", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "message", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.UnsupportedContentTypeFilter", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IActionFilter" + ], + "Members": [ + { + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderFactoryExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "list", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TValueProviderFactory", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProviderFactory" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "list", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.CompositeClientModelValidatorProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidatorProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ValidatorProviders", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "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": "providers", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.CompositeModelValidatorProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ValidatorProviders", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "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": "providers", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "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", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidatorProviderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "list", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TModelValidatorProvider", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "list", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidateNeverAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IPropertyValidationFilter" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ShouldValidateEntry", + "Parameters": [ + { + "Name": "entry", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationEntry" + }, + { + "Name": "parentEntry", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationEntry" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IPropertyValidationFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor", + "Visibility": "Public", + "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", + "Parameters": [ + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "System.Boolean", + "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", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "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", + "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" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.BindingMetadata", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BindingSource", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BinderModelName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BinderModelName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BinderType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BinderType", + "Parameters": [ + { + "Name": "value", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsBindingAllowed", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsBindingAllowed", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsBindingRequired", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsBindingRequired", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReadOnly", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsReadOnly", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelBindingMessageProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelBindingMessageProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelBindingMessageProvider", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelBindingMessageProvider" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyFilterProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PropertyFilterProvider", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.BindingMetadataProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Attributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Key", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ParameterAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_TypeAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.BindingMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "key", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity" + }, + { + "Name": "attributes", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelAttributes" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.BindingSourceMetadataProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IBindingMetadataProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Type", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "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": [ + { + "Name": "type", + "Type": "System.Type" + }, + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultMetadataDetails", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ModelAttributes", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelAttributes", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.BindingMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BindingMetadata", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.BindingMetadata" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DisplayMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DisplayMetadata", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DisplayMetadata" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Key", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata[]", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Properties", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata[]" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyGetter", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PropertyGetter", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertySetter", + "Parameters": [], + "ReturnType": "System.Action", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PropertySetter", + "Parameters": [ + { + "Name": "value", + "Type": "System.Action" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidationMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValidationMetadata", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadata" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContainerMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ContainerMetadata", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "key", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity" + }, + { + "Name": "attributes", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelAttributes" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelBindingMessageProvider", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelBindingMessageProvider", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_MissingBindRequiredValueAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetMissingBindRequiredValueAccessor", + "Parameters": [ + { + "Name": "missingBindRequiredValueAccessor", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MissingKeyOrValueAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetMissingKeyOrValueAccessor", + "Parameters": [ + { + "Name": "missingKeyOrValueAccessor", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MissingRequestBodyRequiredValueAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetMissingRequestBodyRequiredValueAccessor", + "Parameters": [ + { + "Name": "missingRequestBodyRequiredValueAccessor", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueMustNotBeNullAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetValueMustNotBeNullAccessor", + "Parameters": [ + { + "Name": "valueMustNotBeNullAccessor", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AttemptedValueIsInvalidAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetAttemptedValueIsInvalidAccessor", + "Parameters": [ + { + "Name": "attemptedValueIsInvalidAccessor", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_NonPropertyAttemptedValueIsInvalidAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetNonPropertyAttemptedValueIsInvalidAccessor", + "Parameters": [ + { + "Name": "nonPropertyAttemptedValueIsInvalidAccessor", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_UnknownValueIsInvalidAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetUnknownValueIsInvalidAccessor", + "Parameters": [ + { + "Name": "unknownValueIsInvalidAccessor", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_NonPropertyUnknownValueIsInvalidAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetNonPropertyUnknownValueIsInvalidAccessor", + "Parameters": [ + { + "Name": "nonPropertyUnknownValueIsInvalidAccessor", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueIsInvalidAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetValueIsInvalidAccessor", + "Parameters": [ + { + "Name": "valueIsInvalidAccessor", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueMustBeANumberAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetValueMustBeANumberAccessor", + "Parameters": [ + { + "Name": "valueMustBeANumberAccessor", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_NonPropertyValueMustBeANumberAccessor", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetNonPropertyValueMustBeANumberAccessor", + "Parameters": [ + { + "Name": "nonPropertyValueMustBeANumberAccessor", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "originalProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelBindingMessageProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadata", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelAttributes", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContainerMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.BindingMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DisplayMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidationMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AdditionalValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyDictionary", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BinderModelName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BinderType", + "Parameters": [], + "ReturnType": "System.Type", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ConvertEmptyStringToNull", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DataTypeName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Description", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayFormatString", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_EditFormatString", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ElementMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_EnumGroupedDisplayNamesAndValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable>", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_EnumNamesAndValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyDictionary", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HasNonDefaultEditFormat", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HideSurroundingHtml", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HtmlEncode", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsBindingAllowed", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsBindingRequired", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsEnum", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsFlagsEnum", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReadOnly", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsRequired", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelBindingMessageProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelBindingMessageProvider", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_NullDisplayText", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Int32", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Placeholder", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelPropertyCollection", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyFilterProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IPropertyFilterProvider", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ShowForDisplay", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ShowForEdit", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SimpleDisplayProperty", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_TemplateHint", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyValidationFilter", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IPropertyValidationFilter", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidateChildren", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidatorMetadata", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyGetter", + "Parameters": [], + "ReturnType": "System.Func", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertySetter", + "Parameters": [], + "ReturnType": "System.Action", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "provider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "detailsProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ICompositeMetadataDetailsProvider" + }, + { + "Name": "details", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultMetadataDetails" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "provider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "detailsProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ICompositeMetadataDetailsProvider" + }, + { + "Name": "details", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultMetadataDetails" + }, + { + "Name": "modelBindingMessageProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelBindingMessageProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadataProvider", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ICompositeMetadataDetailsProvider", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelBindingMessageProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelBindingMessageProvider", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetMetadataForParameter", + "Parameters": [ + { + "Name": "parameter", + "Type": "System.Reflection.ParameterInfo" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateModelMetadata", + "Parameters": [ + { + "Name": "entry", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultMetadataDetails" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreatePropertyDetails", + "Parameters": [ + { + "Name": "key", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultMetadataDetails[]", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateTypeDetails", + "Parameters": [ + { + "Name": "key", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultMetadataDetails", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "detailsProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ICompositeMetadataDetailsProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "detailsProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ICompositeMetadataDetailsProvider" + }, + { + "Name": "optionsAccessor", + "Type": "Microsoft.Extensions.Options.IOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DisplayMetadata", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_AdditionalValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ConvertEmptyStringToNull", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ConvertEmptyStringToNull", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DataTypeName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DataTypeName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Description", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Description", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayFormatString", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DisplayFormatString", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "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", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DisplayName", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_EditFormatString", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_EditFormatString", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "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", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_EnumGroupedDisplayNamesAndValues", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IEnumerable>" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_EnumNamesAndValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_EnumNamesAndValues", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IReadOnlyDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HasNonDefaultEditFormat", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HasNonDefaultEditFormat", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HideSurroundingHtml", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HideSurroundingHtml", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HtmlEncode", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HtmlEncode", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsEnum", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsEnum", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsFlagsEnum", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsFlagsEnum", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_NullDisplayText", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_NullDisplayText", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "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", + "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": "get_Placeholder", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Placeholder", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ShowForDisplay", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ShowForDisplay", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ShowForEdit", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ShowForEdit", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SimpleDisplayProperty", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SimpleDisplayProperty", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_TemplateHint", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_TemplateHint", + "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.ModelBinding.Metadata.DisplayMetadataProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Attributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DisplayMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Key", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_TypeAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "key", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity" + }, + { + "Name": "attributes", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelAttributes" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ExcludeBindingMetadataProvider", + "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": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IBindingMetadataProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IMetadataDetailsProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CreateBindingMetadata", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.BindingMetadataProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ICompositeMetadataDetailsProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IBindingMetadataProvider", + "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IDisplayMetadataProvider", + "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IValidationMetadataProvider" + ], + "Members": [], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IDisplayMetadataProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IMetadataDetailsProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CreateDisplayMetadata", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DisplayMetadataProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IMetadataDetailsProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IValidationMetadataProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IMetadataDetailsProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CreateValidationMetadata", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadataProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.MetadataDetailsProviderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "list", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TMetadataDetailsProvider", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IMetadataDetailsProvider" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "list", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadata", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_IsRequired", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsRequired", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyValidationFilter", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IPropertyValidationFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PropertyValidationFilter", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IPropertyValidationFilter" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidateChildren", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValidateChildren", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidatorMetadata", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadataProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Attributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Key", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_TypeAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidationMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "key", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity" + }, + { + "Name": "attributes", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelAttributes" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ArrayModelBinder", + "Visibility": "Public", + "Kind": "Class", + "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": "CreateEmptyCollection", + "Parameters": [ + { + "Name": "targetType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Object", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConvertToCollectionType", + "Parameters": [ + { + "Name": "targetType", + "Type": "System.Type" + }, + { + "Name": "collection", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Object", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CopyToModel", + "Parameters": [ + { + "Name": "target", + "Type": "System.Object" + }, + { + "Name": "sourceCollection", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "elementBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + } + ], + "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": [ + { + "ParameterName": "TElement", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ArrayModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BinderTypeModelBinder", + "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": [ + { + "Name": "binderType", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BinderTypeModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder", + "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": [ + { + "Name": "formatters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "readerFactory", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "formatters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "readerFactory", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "formatters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "readerFactory", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinderProvider", + "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": "formatters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "readerFactory", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "formatters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "readerFactory", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "formatters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "readerFactory", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ByteArrayModelBinder", + "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": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ByteArrayModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CancellationTokenModelBinder", + "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": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CancellationTokenModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CollectionModelBinder", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.ICollectionModelBinder" + ], + "Members": [ + { + "Kind": "Method", + "Name": "BindModelAsync", + "Parameters": [ + { + "Name": "bindingContext", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", + "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", + "Parameters": [ + { + "Name": "targetType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.ICollectionModelBinder", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateEmptyCollection", + "Parameters": [ + { + "Name": "targetType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateInstance", + "Parameters": [ + { + "Name": "targetType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Object", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConvertToCollectionType", + "Parameters": [ + { + "Name": "targetType", + "Type": "System.Type" + }, + { + "Name": "collection", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CopyToModel", + "Parameters": [ + { + "Name": "target", + "Type": "System.Object" + }, + { + "Name": "sourceCollection", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "elementBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + } + ], + "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": [ + { + "ParameterName": "TElement", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CollectionModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder", + "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": "Method", + "Name": "CanBindProperty", + "Parameters": [ + { + "Name": "bindingContext", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" + }, + { + "Name": "propertyMetadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BindProperty", + "Parameters": [ + { + "Name": "bindingContext", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateModel", + "Parameters": [ + { + "Name": "bindingContext", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" + } + ], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetProperty", + "Parameters": [ + { + "Name": "bindingContext", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" + }, + { + "Name": "modelName", + "Type": "System.String" + }, + { + "Name": "propertyMetadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "propertyBinders", + "Type": "System.Collections.Generic.IDictionary" + } + ], + "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.DecimalModelBinder", + "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": [ + { + "Name": "supportedStyles", + "Type": "System.Globalization.NumberStyles" + } + ], + "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.DictionaryModelBinder", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [ + { + "Name": "bindingContext", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConvertToCollectionType", + "Parameters": [ + { + "Name": "targetType", + "Type": "System.Type" + }, + { + "Name": "collection", + "Type": "System.Collections.Generic.IEnumerable>" + } + ], + "ReturnType": "System.Object", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateEmptyCollection", + "Parameters": [ + { + "Name": "targetType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Object", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "keyBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + }, + { + "Name": "valueBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + } + ], + "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": [ + { + "ParameterName": "TKey", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + }, + { + "ParameterName": "TValue", + "ParameterPosition": 1, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.DictionaryModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.DoubleModelBinder", + "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": [ + { + "Name": "supportedStyles", + "Type": "System.Globalization.NumberStyles" + } + ], + "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FloatingPointTypeModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FloatModelBinder", + "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": [ + { + "Name": "supportedStyles", + "Type": "System.Globalization.NumberStyles" + } + ], + "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.FormCollectionModelBinder", + "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": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FormCollectionModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FormFileModelBinder", + "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": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.FormFileModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.HeaderModelBinder", + "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": "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.HeaderModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.KeyValuePairModelBinder", + "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": [ + { + "Name": "keyBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + }, + { + "Name": "valueBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + } + ], + "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": [ + { + "ParameterName": "TKey", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + }, + { + "ParameterName": "TValue", + "ParameterPosition": 1, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.KeyValuePairModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ServicesModelBinder", + "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": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ServicesModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.SimpleTypeModelBinder", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "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", + "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": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.SimpleTypeModelBinderProvider", + "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": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.ActionContextAccessor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionContextAccessor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionContextAccessor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionContextAccessor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.ActionDescriptorCollection", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Items", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Version", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "items", + "Type": "System.Collections.Generic.IReadOnlyList" + }, + { + "Name": "version", + "Type": "System.Int32" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorChangeProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetChangeToken", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Primitives.IChangeToken", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorCollectionProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionDescriptors", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Infrastructure.ActionDescriptorCollection", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionInvokerFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateInvoker", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvoker", + "GenericParameter": [] + } + ], + "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", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "SelectCandidates", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Routing.RouteContext" + } + ], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "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", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.Internal.IFormatFilter", + "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", + "Microsoft.AspNetCore.Mvc.Filters.IResultFilter" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetFormat", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.Internal.IFormatFilter", + "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": "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": "options", + "Type": "Microsoft.Extensions.Options.IOptions" + } + ], + "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.Formatters.FormatterMappings", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "SetMediaTypeMappingForFormat", + "Parameters": [ + { + "Name": "format", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetMediaTypeMappingForFormat", + "Parameters": [ + { + "Name": "format", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetMediaTypeMappingForFormat", + "Parameters": [ + { + "Name": "format", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ClearMediaTypeMappingForFormat", + "Parameters": [ + { + "Name": "format", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.HttpNoContentOutputFormatter", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_TreatNullValueAsNoContent", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_TreatNullValueAsNoContent", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CanWriteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterCanWriteContext" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatter", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatter", + "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiRequestFormatMetadataProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_SupportedMediaTypes", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetDefaultValueForType", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CanRead", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CanReadType", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReadAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReadRequestBodyAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetSupportedContentTypes", + "Parameters": [ + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "objectType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiRequestFormatMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.MediaType", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Type", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Primitives.StringSegment", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MatchesAllTypes", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SubType", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Primitives.StringSegment", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SubTypeWithoutSuffix", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Primitives.StringSegment", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SubTypeSuffix", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Primitives.StringSegment", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MatchesAllSubTypes", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MatchesAllSubTypesWithoutSuffix", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Encoding", + "Parameters": [], + "ReturnType": "System.Text.Encoding", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Charset", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Primitives.StringSegment", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HasWildcard", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IsSubsetOf", + "Parameters": [ + { + "Name": "set", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaType" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetParameter", + "Parameters": [ + { + "Name": "parameterName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Primitives.StringSegment", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetParameter", + "Parameters": [ + { + "Name": "parameterName", + "Type": "Microsoft.Extensions.Primitives.StringSegment" + } + ], + "ReturnType": "Microsoft.Extensions.Primitives.StringSegment", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReplaceEncoding", + "Parameters": [ + { + "Name": "mediaType", + "Type": "System.String" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.String", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReplaceEncoding", + "Parameters": [ + { + "Name": "mediaType", + "Type": "Microsoft.Extensions.Primitives.StringSegment" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.String", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetEncoding", + "Parameters": [ + { + "Name": "mediaType", + "Type": "System.String" + } + ], + "ReturnType": "System.Text.Encoding", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetEncoding", + "Parameters": [ + { + "Name": "mediaType", + "Type": "Microsoft.Extensions.Primitives.StringSegment" + } + ], + "ReturnType": "System.Text.Encoding", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateMediaTypeSegmentWithQuality", + "Parameters": [ + { + "Name": "mediaType", + "Type": "System.String" + }, + { + "Name": "start", + "Type": "System.Int32" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.Internal.MediaTypeSegmentWithQuality", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "mediaType", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "mediaType", + "Type": "Microsoft.Extensions.Primitives.StringSegment" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "mediaType", + "Type": "System.String" + }, + { + "Name": "offset", + "Type": "System.Int32" + }, + { + "Name": "length", + "Type": "System.Nullable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Collections.ObjectModel.Collection", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "item", + "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Insert", + "Parameters": [ + { + "Name": "index", + "Type": "System.Int32" + }, + { + "Name": "item", + "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Remove", + "Parameters": [ + { + "Name": "item", + "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatter", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseTypeMetadataProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_SupportedMediaTypes", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CanWriteType", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetSupportedContentTypes", + "Parameters": [ + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "objectType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseTypeMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CanWriteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterCanWriteContext" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteResponseHeaders", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteResponseBodyAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.StreamOutputFormatter", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CanWriteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterCanWriteContext" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.StringOutputFormatter", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CanWriteResult", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterCanWriteContext" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteResponseBodyAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatter", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_SupportedEncodings", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReadRequestBodyAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReadRequestBodyAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SelectCharacterEncoding", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + } + ], + "ReturnType": "System.Text.Encoding", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "UTF8EncodingWithoutBOM", + "Parameters": [], + "ReturnType": "System.Text.Encoding", + "Static": true, + "ReadOnly": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "UTF16EncodingLittleEndian", + "Parameters": [], + "ReturnType": "System.Text.Encoding", + "Static": true, + "ReadOnly": true, + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "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", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SelectCharacterEncoding", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + } + ], + "ReturnType": "System.Text.Encoding", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteResponseBodyAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Sealed": true, + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IActionFilter", + "Microsoft.AspNetCore.Mvc.Filters.IAsyncActionFilter", + "Microsoft.AspNetCore.Mvc.Filters.IResultFilter", + "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": "OnActionExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext" + } + ], + "ReturnType": "System.Void", + "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", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter", + "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", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncActionFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnResultExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext" + } + ], + "ReturnType": "System.Void", + "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", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IResultFilter", + "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", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ExceptionFilterAttribute", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IAsyncExceptionFilter", + "Microsoft.AspNetCore.Mvc.Filters.IExceptionFilter", + "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": "OnExceptionAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncExceptionFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnException", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IExceptionFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.FilterCollection", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Collections.ObjectModel.Collection", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Add", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TFilterType", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "filterType", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "order", + "Type": "System.Int32" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TFilterType", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "filterType", + "Type": "System.Type" + }, + { + "Name": "order", + "Type": "System.Int32" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddService", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TFilterType", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "AddService", + "Parameters": [ + { + "Name": "filterType", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddService", + "Parameters": [ + { + "Name": "order", + "Type": "System.Int32" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TFilterType", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "AddService", + "Parameters": [ + { + "Name": "filterType", + "Type": "System.Type" + }, + { + "Name": "order", + "Type": "System.Int32" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.FilterScope", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Field", + "Name": "First", + "Parameters": [], + "ReturnType": "System.Int32", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Global", + "Parameters": [], + "ReturnType": "System.Int32", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Controller", + "Parameters": [], + "ReturnType": "System.Int32", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Action", + "Parameters": [], + "ReturnType": "System.Int32", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "Last", + "Parameters": [], + "ReturnType": "System.Int32", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.ResultFilterAttribute", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IResultFilter", + "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": "OnResultExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext" + } + ], + "ReturnType": "System.Void", + "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", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IResultFilter", + "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", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ControllerName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ControllerName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ActionName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MethodInfo", + "Parameters": [], + "ReturnType": "System.Reflection.MethodInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MethodInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.MethodInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ControllerTypeInfo", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ControllerTypeInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.TypeInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayName", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DisplayName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActivatorProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivatorProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CreateActivator", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" + } + ], + "ReturnType": "System.Func", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivatorProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateReleaser", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" + } + ], + "ReturnType": "System.Action", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivatorProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "controllerActivator", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.ControllerBoundPropertyDescriptor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_PropertyInfo", + "Parameters": [], + "ReturnType": "System.Reflection.PropertyInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PropertyInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.PropertyInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.ControllerFactoryProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Controllers.IControllerFactoryProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CreateControllerFactory", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" + } + ], + "ReturnType": "System.Func", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Controllers.IControllerFactoryProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateControllerReleaser", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" + } + ], + "ReturnType": "System.Action", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Controllers.IControllerFactoryProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "activatorProvider", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivatorProvider" + }, + { + "Name": "controllerFactory", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.IControllerFactory" + }, + { + "Name": "propertyActivators", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.ControllerFeature", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Controllers", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.ControllerFeatureProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "PopulateFeature", + "Parameters": [ + { + "Name": "parts", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "feature", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerFeature" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IsController", + "Parameters": [ + { + "Name": "typeInfo", + "Type": "System.Reflection.TypeInfo" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.ControllerParameterDescriptor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ParameterInfo", + "Parameters": [], + "ReturnType": "System.Reflection.ParameterInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ParameterInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.ParameterInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.DefaultControllerActivator", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "controllerContext", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + } + ], + "ReturnType": "System.Object", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Release", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + }, + { + "Name": "controller", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "typeActivatorCache", + "Type": "Microsoft.AspNetCore.Mvc.Internal.ITypeActivatorCache" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.DefaultControllerFactory", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Controllers.IControllerFactory" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ControllerActivator", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateController", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + } + ], + "ReturnType": "System.Object", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Controllers.IControllerFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReleaseController", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + }, + { + "Name": "controller", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Controllers.IControllerFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "controllerActivator", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator" + }, + { + "Name": "propertyActivators", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + } + ], + "ReturnType": "System.Object", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Release", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + }, + { + "Name": "controller", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivatorProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateActivator", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" + } + ], + "ReturnType": "System.Func", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateReleaser", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" + } + ], + "ReturnType": "System.Action", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.IControllerFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateController", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + } + ], + "ReturnType": "System.Object", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReleaseController", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + }, + { + "Name": "controller", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.IControllerFactoryProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateControllerFactory", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" + } + ], + "ReturnType": "System.Func", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateControllerReleaser", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" + } + ], + "ReturnType": "System.Action", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Controllers.ServiceBasedControllerActivator", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + } + ], + "ReturnType": "System.Object", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Release", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" + }, + { + "Name": "controller", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Controllers.IControllerActivator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Authorization.AllowAnonymousFilter", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Authorization.IAllowAnonymousFilter" + ], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IAsyncAuthorizationFilter", + "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_PolicyProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AuthorizeData", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Policy", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Authorization.AuthorizationPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnAuthorizationAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncAuthorizationFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "policy", + "Type": "Microsoft.AspNetCore.Authorization.AuthorizationPolicy" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "policyProvider", + "Type": "Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider" + }, + { + "Name": "authorizeData", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "authorizeData", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "policy", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPart", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_FeatureProviders", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ApplicationParts", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "PopulateFeature", + "Parameters": [ + { + "Name": "feature", + "Type": "T0" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TFeature", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.AssemblyPart", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPart", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationPartTypeProvider", + "Microsoft.AspNetCore.Mvc.ApplicationParts.ICompilationReferencesProvider" + ], + "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": "Method", + "Name": "get_Types", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationPartTypeProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetReferencePaths", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationParts.ICompilationReferencesProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "PopulateFeature", + "Parameters": [ + { + "Name": "parts", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "feature", + "Type": "T0" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TFeature", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationPartTypeProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Types", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.ICompilationReferencesProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetReferencePaths", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel", + "Microsoft.AspNetCore.Mvc.ApplicationModels.IFilterModel", + "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", + "Parameters": [], + "ReturnType": "System.Reflection.MethodInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ActionName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ApiExplorer", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApiExplorerModel", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApiExplorerModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ApiExplorer", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApiExplorerModel" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApiExplorerModel", + "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_Controller", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Controller", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Filters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IFilterModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Parameters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Selectors", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionMethod", + "Type": "System.Reflection.MethodInfo" + }, + { + "Name": "attributes", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApiExplorerModel", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_IsVisible", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsVisible", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "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": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApiExplorerModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", + "Microsoft.AspNetCore.Mvc.ApplicationModels.IFilterModel", + "Microsoft.AspNetCore.Mvc.ApplicationModels.IApiExplorerModel" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ApiExplorer", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApiExplorerModel", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApiExplorerModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ApiExplorer", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApiExplorerModel" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApiExplorerModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Controllers", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Filters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IFilterModel", + "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", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ControllerTypes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "controllerTypes", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.AttributeRouteModel", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Attribute", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Template", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Template", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Order", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "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_SuppressLinkGeneration", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SuppressLinkGeneration", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SuppressPathMatching", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SuppressPathMatching", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsAbsoluteTemplate", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CombineAttributeRouteModel", + "Parameters": [ + { + "Name": "left", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.AttributeRouteModel" + }, + { + "Name": "right", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.AttributeRouteModel" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.AttributeRouteModel", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CombineTemplates", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + }, + { + "Name": "template", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IsOverridePattern", + "Parameters": [ + { + "Name": "template", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReplaceTokens", + "Parameters": [ + { + "Name": "template", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Collections.Generic.IDictionary" + } + ], + "ReturnType": "System.String", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "templateProvider", + "Type": "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.AttributeRouteModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel", + "Microsoft.AspNetCore.Mvc.ApplicationModels.IFilterModel", + "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", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ApiExplorer", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApiExplorerModel", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApiExplorerModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ApiExplorer", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApiExplorerModel" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApiExplorerModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Application", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Application", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel" + } + ], + "ReturnType": "System.Void", + "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_ControllerName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ControllerName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ControllerType", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ControllerProperties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Filters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IFilterModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Selectors", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "controllerType", + "Type": "System.Reflection.TypeInfo" + }, + { + "Name": "attributes", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IActionModelConvention", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Apply", + "Parameters": [ + { + "Name": "action", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApiExplorerModel", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ApiExplorer", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApiExplorerModel", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ApiExplorer", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApiExplorerModel" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelConvention", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Apply", + "Parameters": [ + { + "Name": "application", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", + "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.ApplicationModels.ApplicationModelProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_BindingInfo", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BindingInfo", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Attributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MemberInfo", + "Parameters": [], + "ReturnType": "System.Reflection.MemberInfo", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IControllerModelConvention", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Apply", + "Parameters": [ + { + "Name": "controller", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IFilterModel", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Filters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "GenericParameter": [] + } + ], + "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", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Apply", + "Parameters": [ + { + "Name": "parameter", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModel" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModel", + "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_Action", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Action", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel" + } + ], + "ReturnType": "System.Void", + "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_ParameterInfo", + "Parameters": [], + "ReturnType": "System.Reflection.ParameterInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ParameterName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ParameterName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DisplayName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "parameterInfo", + "Type": "System.Reflection.ParameterInfo" + }, + { + "Name": "attributes", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModelBase", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Controller", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel" + } + ], + "ReturnType": "System.Void", + "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_PropertyInfo", + "Parameters": [], + "ReturnType": "System.Reflection.PropertyInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PropertyName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "propertyInfo", + "Type": "System.Reflection.PropertyInfo" + }, + { + "Name": "attributes", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PropertyModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.SelectorModel", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_AttributeRouteModel", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.AttributeRouteModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AttributeRouteModel", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.AttributeRouteModel" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ActionConstraints", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.SelectorModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupNameProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_GroupName", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionVisibilityProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_IgnoreApi", + "Parameters": [], + "ReturnType": "System.Boolean", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiRequestFormatMetadataProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetSupportedContentTypes", + "Parameters": [ + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "objectType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiRequestMetadataProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "SetContentTypes", + "Parameters": [ + { + "Name": "contentTypes", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseMetadataProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Type", + "Parameters": [], + "ReturnType": "System.Type", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_StatusCode", + "Parameters": [], + "ReturnType": "System.Int32", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetContentTypes", + "Parameters": [ + { + "Name": "contentTypes", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseTypeMetadataProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetSupportedContentTypes", + "Parameters": [ + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "objectType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionMethodSelectorAttribute", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint" + ], + "Members": [ + { + "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": "set_Order", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "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": "IsValidForRequest", + "Parameters": [ + { + "Name": "routeContext", + "Type": "Microsoft.AspNetCore.Routing.RouteContext" + }, + { + "Name": "action", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.ApplicationModelConventionExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "list", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TApplicationModelConvention", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelConvention" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "list", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "conventions", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "controllerModelConvention", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.IControllerModelConvention" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "conventions", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "actionModelConvention", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.IActionModelConvention" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "conventions", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "parameterModelConvention", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.IParameterModelConvention" + } + ], + "ReturnType": "System.Void", + "Static": true, + "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": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Services", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PartManager", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Services", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PartManager", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcCoreMvcBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddMvcOptions", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddFormatterMappings", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddApplicationPart", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConfigureApplicationPartManager", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddControllersAsServices", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "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": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcCoreMvcCoreBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddMvcOptions", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddFormatterMappings", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddFormatterMappings", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddAuthorization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddAuthorization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddControllersAsServices", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddApplicationPart", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConfigureApplicationPartManager", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "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": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcCoreServiceCollectionExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddMvcCore", + "Parameters": [ + { + "Name": "services", + "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcCore", + "Parameters": [ + { + "Name": "services", + "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "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": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Exists", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Exists", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Length", + "Parameters": [], + "ReturnType": "System.Int64", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Length", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int64" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_LastModified", + "Parameters": [], + "ReturnType": "System.DateTimeOffset", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_LastModified", + "Parameters": [ + { + "Name": "value", + "Type": "System.DateTimeOffset" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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": [] + } + ] +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/CorsAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/CorsAuthorizationFilter.cs new file mode 100644 index 0000000000..0d853eae9d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/CorsAuthorizationFilter.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.Threading.Tasks; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Cors.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Cors +{ + /// + /// A filter that applies the given and adds appropriate response headers. + /// + public class CorsAuthorizationFilter : ICorsAuthorizationFilter + { + private readonly ICorsService _corsService; + private readonly ICorsPolicyProvider _corsPolicyProvider; + private readonly ILogger _logger; + + /// + /// Creates a new instance of . + /// + /// The . + /// The . + public CorsAuthorizationFilter(ICorsService corsService, ICorsPolicyProvider policyProvider) + : this(corsService, policyProvider, NullLoggerFactory.Instance) + { + } + + /// + /// Creates a new instance of . + /// + /// The . + /// The . + /// The . + public CorsAuthorizationFilter( + ICorsService corsService, + ICorsPolicyProvider policyProvider, + ILoggerFactory loggerFactory) + { + if (corsService == null) + { + throw new ArgumentNullException(nameof(corsService)); + } + + if (policyProvider == null) + { + throw new ArgumentNullException(nameof(policyProvider)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _corsService = corsService; + _corsPolicyProvider = policyProvider; + _logger = loggerFactory.CreateLogger(GetType()); + } + + /// + /// The policy name used to fetch a . + /// + public string PolicyName { get; set; } + + /// + // Since clients' preflight requests would not have data to authenticate requests, this + // filter must run before any other authorization filters. + public int Order => int.MinValue + 100; + + /// + public async Task OnAuthorizationAsync(Filters.AuthorizationFilterContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // If this filter is not closest to the action, it is not applicable. + if (!context.IsEffectivePolicy(this)) + { + _logger.NotMostEffectiveFilter(typeof(ICorsAuthorizationFilter)); + return; + } + + var httpContext = context.HttpContext; + var request = httpContext.Request; + if (request.Headers.ContainsKey(CorsConstants.Origin)) + { + var policy = await _corsPolicyProvider.GetPolicyAsync(httpContext, PolicyName); + + if (policy == null) + { + throw new InvalidOperationException( + Resources.FormatCorsAuthorizationFilter_MissingCorsPolicy(PolicyName)); + } + + var result = _corsService.EvaluatePolicy(context.HttpContext, policy); + _corsService.ApplyResult(result, context.HttpContext.Response); + + var accessControlRequestMethod = + httpContext.Request.Headers[CorsConstants.AccessControlRequestMethod]; + if (string.Equals( + request.Method, + CorsConstants.PreflightHttpMethod, + StringComparison.OrdinalIgnoreCase) && + !StringValues.IsNullOrEmpty(accessControlRequestMethod)) + { + // If this was a preflight, there is no need to run anything else. + // Also the response is always 200 so that anyone after mvc can handle the pre flight request. + context.Result = new StatusCodeResult(StatusCodes.Status200OK); + } + + // Continue with other filters and action. + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/DependencyInjection/MvcCorsMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/DependencyInjection/MvcCorsMvcCoreBuilderExtensions.cs new file mode 100644 index 0000000000..a4cf5c1800 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/DependencyInjection/MvcCorsMvcCoreBuilderExtensions.cs @@ -0,0 +1,74 @@ +// Copyright (c) .NET 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.Cors.Infrastructure; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Cors; +using Microsoft.AspNetCore.Mvc.Cors.Internal; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class MvcCorsMvcCoreBuilderExtensions + { + public static IMvcCoreBuilder AddCors(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddCorsServices(builder.Services); + return builder; + } + + public static IMvcCoreBuilder AddCors( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + AddCorsServices(builder.Services); + builder.Services.Configure(setupAction); + + return builder; + } + + public static IMvcCoreBuilder ConfigureCors( + 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; + } + + // Internal for testing. + internal static void AddCorsServices(IServiceCollection services) + { + services.AddCors(); + + services.TryAddEnumerable( + ServiceDescriptor.Transient()); + services.TryAddTransient(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs new file mode 100644 index 0000000000..4410412e28 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs @@ -0,0 +1,91 @@ +// Copyright (c) .NET 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.Cors.Infrastructure; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Cors.Internal +{ + public class CorsApplicationModelProvider : IApplicationModelProvider + { + public int Order => -1000 + 10; + + public void OnProvidersExecuted(ApplicationModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + // Intentionally empty. + } + + public void OnProvidersExecuting(ApplicationModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var isCorsEnabledGlobally = context.Result.Filters.OfType().Any() || + context.Result.Filters.OfType().Any(); + + foreach (var controllerModel in context.Result.Controllers) + { + var enableCors = controllerModel.Attributes.OfType().FirstOrDefault(); + if (enableCors != null) + { + controllerModel.Filters.Add(new CorsAuthorizationFilterFactory(enableCors.PolicyName)); + } + + var disableCors = controllerModel.Attributes.OfType().FirstOrDefault(); + if (disableCors != null) + { + controllerModel.Filters.Add(new DisableCorsAuthorizationFilter()); + } + + var corsOnController = enableCors != null || disableCors != null || controllerModel.Filters.OfType().Any(); + + foreach (var actionModel in controllerModel.Actions) + { + enableCors = actionModel.Attributes.OfType().FirstOrDefault(); + if (enableCors != null) + { + actionModel.Filters.Add(new CorsAuthorizationFilterFactory(enableCors.PolicyName)); + } + + disableCors = actionModel.Attributes.OfType().FirstOrDefault(); + if (disableCors != null) + { + actionModel.Filters.Add(new DisableCorsAuthorizationFilter()); + } + + var corsOnAction = enableCors != null || disableCors != null || actionModel.Filters.OfType().Any(); + + if (isCorsEnabledGlobally || corsOnController || corsOnAction) + { + UpdateHttpMethodActionConstraint(actionModel); + } + } + } + } + + private static void UpdateHttpMethodActionConstraint(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) + { + selectorModel.ActionConstraints[j] = new CorsHttpMethodActionConstraint(httpConstraint); + } + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsAuthorizationFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsAuthorizationFilterFactory.cs new file mode 100644 index 0000000000..9cb7146f5c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsAuthorizationFilterFactory.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; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Cors.Internal +{ + /// + /// A filter factory which creates a new instance of . + /// + public class CorsAuthorizationFilterFactory : IFilterFactory, IOrderedFilter + { + private readonly string _policyName; + + /// + /// Creates a new instance of . + /// + /// Name used to fetch a CORS policy. + public CorsAuthorizationFilterFactory(string policyName) + { + _policyName = policyName; + } + + /// + // Since clients' preflight requests would not have data to authenticate requests, this + // filter must run before any other authorization filters. + public int Order => int.MinValue + 100; + + /// + public bool IsReusable => true; + + /// + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + var filter = serviceProvider.GetRequiredService(); + filter.PolicyName = _policyName; + return filter; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs new file mode 100644 index 0000000000..71ba2d696c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET 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 Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.Extensions.Primitives; +using Microsoft.AspNetCore.Mvc.Internal; + +namespace Microsoft.AspNetCore.Mvc.Cors.Internal +{ + public class CorsHttpMethodActionConstraint : HttpMethodActionConstraint + { + private readonly string OriginHeader = "Origin"; + private readonly string AccessControlRequestMethod = "Access-Control-Request-Method"; + private readonly string PreflightHttpMethod = "OPTIONS"; + + public CorsHttpMethodActionConstraint(HttpMethodActionConstraint constraint) + : base(constraint.HttpMethods) + { + } + + public override bool Accept(ActionConstraintContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var methods = (ReadOnlyCollection)HttpMethods; + if (methods.Count == 0) + { + return true; + } + + var request = context.RouteContext.HttpContext.Request; + // Perf: Check http method before accessing the Headers collection. + if (string.Equals(request.Method, PreflightHttpMethod, StringComparison.OrdinalIgnoreCase) && + request.Headers.ContainsKey(OriginHeader) && + request.Headers.TryGetValue(AccessControlRequestMethod, out var accessControlRequestMethod) && + !StringValues.IsNullOrEmpty(accessControlRequestMethod)) + { + for (var i = 0; i < methods.Count; i++) + { + var supportedMethod = methods[i]; + if (string.Equals(supportedMethod, accessControlRequestMethod, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + return false; + } + + return base.Accept(context); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsLoggerExtensions.cs new file mode 100644 index 0000000000..c3fab172b9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsLoggerExtensions.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.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Cors.Internal +{ + public static class CorsLoggerExtensions + { + private static readonly Action _notMostEffectiveFilter; + + static CorsLoggerExtensions() + { + _notMostEffectiveFilter = LoggerMessage.Define( + LogLevel.Debug, + 1, + "Skipping the execution of current filter as its not the most effective filter implementing the policy {FilterPolicy}."); + } + + public static void NotMostEffectiveFilter(this ILogger logger, Type policyType) + { + _notMostEffectiveFilter(logger, policyType, null); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/DisableCorsAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/DisableCorsAuthorizationFilter.cs new file mode 100644 index 0000000000..8ecd6cbfb6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/DisableCorsAuthorizationFilter.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; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Cors.Internal +{ + /// + /// An which ensures that an action does not run for a pre-flight request. + /// + public class DisableCorsAuthorizationFilter : ICorsAuthorizationFilter + { + /// + // Since clients' preflight requests would not have data to authenticate requests, this + // filter must run before any other authorization filters. + public int Order => int.MinValue + 100; + + /// + public Task OnAuthorizationAsync(AuthorizationFilterContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var accessControlRequestMethod = + context.HttpContext.Request.Headers[CorsConstants.AccessControlRequestMethod]; + if (string.Equals( + context.HttpContext.Request.Method, + CorsConstants.PreflightHttpMethod, + StringComparison.OrdinalIgnoreCase) && + !StringValues.IsNullOrEmpty(accessControlRequestMethod)) + { + // Short circuit if the request is preflight as that should not result in action execution. + context.Result = new StatusCodeResult(StatusCodes.Status200OK); + } + + // Let the action be executed. + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/ICorsAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/ICorsAuthorizationFilter.cs new file mode 100644 index 0000000000..6989ffd421 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/ICorsAuthorizationFilter.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.Filters; + +namespace Microsoft.AspNetCore.Mvc.Cors.Internal +{ + /// + /// A filter that can be used to enable/disable CORS support for a resource. + /// + public interface ICorsAuthorizationFilter : IAsyncAuthorizationFilter, IOrderedFilter + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Microsoft.AspNetCore.Mvc.Cors.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Microsoft.AspNetCore.Mvc.Cors.csproj new file mode 100644 index 0000000000..f6056e62e5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Microsoft.AspNetCore.Mvc.Cors.csproj @@ -0,0 +1,17 @@ + + + + ASP.NET Core MVC cross-origin resource sharing (CORS) features. + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;aspnetcoremvc;cors + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..19603aa2df --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/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.Cors.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..fe4a772e37 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Properties/Resources.Designer.cs @@ -0,0 +1,44 @@ +// +namespace Microsoft.AspNetCore.Mvc.Cors +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Mvc.Cors.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// A CORS policy named '{0}' could not be found. + /// + internal static string CorsAuthorizationFilter_MissingCorsPolicy + { + get => GetString("CorsAuthorizationFilter_MissingCorsPolicy"); + } + + /// + /// A CORS policy named '{0}' could not be found. + /// + internal static string FormatCorsAuthorizationFilter_MissingCorsPolicy(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("CorsAuthorizationFilter_MissingCorsPolicy"), p0); + + 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/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Resources.resx new file mode 100644 index 0000000000..6c4897ab7f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Resources.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 + + + A CORS policy named '{0}' could not be found. + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/baseline.netcore.json new file mode 100644 index 0000000000..d00cbbb16b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/baseline.netcore.json @@ -0,0 +1,165 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Cors, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcCorsMvcCoreBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddCors", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddCors", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConfigureCors", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Cors.CorsAuthorizationFilter", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "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", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PolicyName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "corsService", + "Type": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsService" + }, + { + "Name": "policyProvider", + "Type": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "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", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/AttributeAdapterBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/AttributeAdapterBase.cs new file mode 100644 index 0000000000..6f8d1a0d26 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/AttributeAdapterBase.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 System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations +{ + /// + /// An abstract subclass of which wraps up all the required + /// interfaces for the adapters. + /// + /// The type of which is being wrapped. + public abstract class AttributeAdapterBase : + ValidationAttributeAdapter, + IAttributeAdapter + where TAttribute : ValidationAttribute + { + /// + /// Instantiates a new . + /// + /// The being wrapped. + /// The to be used in error generation. + public AttributeAdapterBase(TAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + } + + /// + public abstract string GetErrorMessage(ModelValidationContextBase validationContext); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcBuilderExtensions.cs new file mode 100644 index 0000000000..ddbd623b3e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcBuilderExtensions.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 Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for configuring MVC data annotations localization. + /// + public static class MvcDataAnnotationsMvcBuilderExtensions + { + /// + /// Adds MVC data annotations localization to the application. + /// + /// The . + /// The . + public static IMvcBuilder AddDataAnnotationsLocalization(this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddDataAnnotationsLocalization(builder, setupAction: null); + } + + /// + /// Adds MVC data annotations localization to the application. + /// + /// The . + /// The action to configure . + /// + /// The . + public static IMvcBuilder AddDataAnnotationsLocalization( + this IMvcBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + DataAnnotationsLocalizationServices.AddDataAnnotationsLocalizationServices( + builder.Services, + setupAction); + + return builder; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcCoreBuilderExtensions.cs new file mode 100644 index 0000000000..160a73345e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcCoreBuilderExtensions.cs @@ -0,0 +1,85 @@ +// Copyright (c) .NET 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.DataAnnotations; +using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extensions for configuring MVC data annotations using an . + /// + public static class MvcDataAnnotationsMvcCoreBuilderExtensions + { + /// + /// Registers MVC data annotations. + /// + /// The . + /// The . + public static IMvcCoreBuilder AddDataAnnotations(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddDataAnnotationsServices(builder.Services); + return builder; + } + + /// + /// Adds MVC data annotations localization to the application. + /// + /// The . + /// The . + public static IMvcCoreBuilder AddDataAnnotationsLocalization(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddDataAnnotationsLocalization(builder, setupAction: null); + } + + /// + /// Registers an action to configure for MVC data + /// annotations localization. + /// + /// The . + /// An . + /// The . + public static IMvcCoreBuilder AddDataAnnotationsLocalization( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddDataAnnotationsLocalizationServices(builder.Services, setupAction); + return builder; + } + + // Internal for testing. + internal static void AddDataAnnotationsServices(IServiceCollection services) + { + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcDataAnnotationsMvcOptionsSetup>()); + services.TryAddSingleton(); + } + + // Internal for testing. + internal static void AddDataAnnotationsLocalizationServices( + IServiceCollection services, + Action setupAction) + { + DataAnnotationsLocalizationServices.AddDataAnnotationsLocalizationServices(services, setupAction); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/HiddenInputAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/HiddenInputAttribute.cs new file mode 100644 index 0000000000..e88393d48c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/HiddenInputAttribute.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; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Indicates associated property or all properties with the associated type should be edited using an + /// <input> element of type "hidden". + /// + /// + /// When overriding a inherited from a base class, should apply both + /// [HiddenInput(DisplayValue = true)] (if the inherited attribute had DisplayValue = false) and a + /// with some value other than "HiddenInput". + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public sealed class HiddenInputAttribute : Attribute + { + /// + /// Instantiates a new instance of the class. + /// + public HiddenInputAttribute() + { + DisplayValue = true; + } + + /// + /// Gets or sets a value indicating whether to display the value as well as provide a hidden <input> + /// element. The default value is true. + /// + /// + /// If false, also causes the default display and editor templates to return HTML + /// lacking the usual per-property <div> wrapper around the associated property and the default display + /// "HiddenInput" template to return string.Empty for the associated property. Thus the default + /// display template effectively skips the property and the default + /// editor template returns only the hidden <input> element for the property. + /// + public bool DisplayValue { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IAttributeAdapter.cs new file mode 100644 index 0000000000..57765c869d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IAttributeAdapter.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.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations +{ + /// + /// Interface so that adapters provide their relevant values to error messages. + /// + public interface IAttributeAdapter : IClientModelValidator + { + /// + /// Gets the error message. + /// + /// The context to use in message creation. + /// The localized error message. + string GetErrorMessage(ModelValidationContextBase validationContext); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IValidationAttributeAdapterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IValidationAttributeAdapterProvider.cs new file mode 100644 index 0000000000..dc973bc9ac --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IValidationAttributeAdapterProvider.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.ComponentModel.DataAnnotations; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations +{ + /// + /// Provider for supplying 's. + /// + public interface IValidationAttributeAdapterProvider + { + /// + /// Returns the for the given . + /// + /// The to create an + /// for. + /// The which will be used to create messages. + /// + /// An for the given . + IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/CompareAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/CompareAttributeAdapter.cs new file mode 100644 index 0000000000..f64272fa04 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/CompareAttributeAdapter.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET 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.DataAnnotations; +using System.Globalization; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + public class CompareAttributeAdapter : AttributeAdapterBase + { + private readonly string _otherProperty; + + public CompareAttributeAdapter(CompareAttribute attribute, IStringLocalizer stringLocalizer) + : base(new CompareAttributeWrapper(attribute), stringLocalizer) + { + _otherProperty = "*." + attribute.OtherProperty; + } + + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-equalto", GetErrorMessage(context)); + MergeAttribute(context.Attributes, "data-val-equalto-other", _otherProperty); + } + + /// + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + var displayName = validationContext.ModelMetadata.GetDisplayName(); + var otherPropertyDisplayName = CompareAttributeWrapper.GetOtherPropertyDisplayName( + validationContext, + Attribute); + + ((CompareAttributeWrapper)Attribute).ValidationContext = validationContext; + + return GetErrorMessage(validationContext.ModelMetadata, displayName, otherPropertyDisplayName); + } + + // TODO: This entire class is needed because System.ComponentModel.DataAnnotations.CompareAttribute doesn't + // populate OtherPropertyDisplayName until you call FormatErrorMessage. + private sealed class CompareAttributeWrapper : CompareAttribute + { + public ModelValidationContextBase ValidationContext { get; set; } + + public CompareAttributeWrapper(CompareAttribute attribute) + : base(attribute.OtherProperty) + { + // Copy settable properties from wrapped attribute. Don't reset default message accessor (set as + // CompareAttribute constructor calls ValidationAttribute constructor) when all properties are null to + // preserve default error message. Reset the message accessor when just ErrorMessageResourceType is + // non-null to ensure correct InvalidOperationException. + if (!string.IsNullOrEmpty(attribute.ErrorMessage) || + !string.IsNullOrEmpty(attribute.ErrorMessageResourceName) || + attribute.ErrorMessageResourceType != null) + { + ErrorMessage = attribute.ErrorMessage; + ErrorMessageResourceName = attribute.ErrorMessageResourceName; + ErrorMessageResourceType = attribute.ErrorMessageResourceType; + } + } + + public override string FormatErrorMessage(string name) + { + var displayName = ValidationContext.ModelMetadata.GetDisplayName(); + return string.Format(CultureInfo.CurrentCulture, + ErrorMessageString, + displayName, + GetOtherPropertyDisplayName(ValidationContext, this)); + } + + public static string GetOtherPropertyDisplayName( + ModelValidationContextBase validationContext, + CompareAttribute attribute) + { + // The System.ComponentModel.DataAnnotations.CompareAttribute doesn't populate the + // OtherPropertyDisplayName until after IsValid() is called. Therefore, at the time we get + // the error message for client validation, the display name is not populated and won't be used. + var otherPropertyDisplayName = attribute.OtherPropertyDisplayName; + if (otherPropertyDisplayName == null && validationContext.ModelMetadata.ContainerType != null) + { + var otherProperty = validationContext.MetadataProvider.GetMetadataForProperty( + validationContext.ModelMetadata.ContainerType, + attribute.OtherProperty); + if (otherProperty != null) + { + return otherProperty.GetDisplayName(); + } + } + + return attribute.OtherProperty; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsClientModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsClientModelValidatorProvider.cs new file mode 100644 index 0000000000..05d2aacac3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsClientModelValidatorProvider.cs @@ -0,0 +1,109 @@ +// Copyright (c) .NET 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.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + /// + /// An implementation of which provides client validators + /// for attributes which derive from . It also provides + /// a validator for types which implement . + /// The logic to support + /// is implemented in . + /// + public class DataAnnotationsClientModelValidatorProvider : IClientModelValidatorProvider + { + private readonly IOptions _options; + private readonly IStringLocalizerFactory _stringLocalizerFactory; + private readonly IValidationAttributeAdapterProvider _validationAttributeAdapterProvider; + + /// + /// Create a new instance of . + /// + /// The + /// that supplies s. + /// The . + /// The . + public DataAnnotationsClientModelValidatorProvider( + IValidationAttributeAdapterProvider validationAttributeAdapterProvider, + IOptions options, + IStringLocalizerFactory stringLocalizerFactory) + { + if (validationAttributeAdapterProvider == null) + { + throw new ArgumentNullException(nameof(validationAttributeAdapterProvider)); + } + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + _validationAttributeAdapterProvider = validationAttributeAdapterProvider; + _options = options; + _stringLocalizerFactory = stringLocalizerFactory; + } + + /// + public void CreateValidators(ClientValidatorProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + IStringLocalizer stringLocalizer = null; + if (_options.Value.DataAnnotationLocalizerProvider != null && _stringLocalizerFactory != null) + { + // This will pass first non-null type (either containerType or modelType) to delegate. + // Pass the root model type(container type) if it is non null, else pass the model type. + stringLocalizer = _options.Value.DataAnnotationLocalizerProvider( + context.ModelMetadata.ContainerType ?? context.ModelMetadata.ModelType, + _stringLocalizerFactory); + } + + var hasRequiredAttribute = false; + + for (var i = 0; i < context.Results.Count; i++) + { + var validatorItem = context.Results[i]; + if (validatorItem.Validator != null) + { + // Check if a required attribute is already cached. + hasRequiredAttribute |= validatorItem.Validator is RequiredAttributeAdapter; + continue; + } + + var attribute = validatorItem.ValidatorMetadata as ValidationAttribute; + if (attribute == null) + { + continue; + } + + hasRequiredAttribute |= attribute is RequiredAttribute; + + var adapter = _validationAttributeAdapterProvider.GetAttributeAdapter(attribute, stringLocalizer); + if (adapter != null) + { + validatorItem.Validator = adapter; + validatorItem.IsReusable = true; + } + } + + if (!hasRequiredAttribute && context.ModelMetadata.IsRequired) + { + // Add a default '[Required]' validator for generating HTML if necessary. + context.Results.Add(new ClientValidatorItem + { + Validator = _validationAttributeAdapterProvider.GetAttributeAdapter( + new RequiredAttribute(), + stringLocalizer), + IsReusable = true + }); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs new file mode 100644 index 0000000000..f54cd9dd5d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.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; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + public static class DataAnnotationsLocalizationServices + { + public static void AddDataAnnotationsLocalizationServices( + IServiceCollection services, + Action setupAction) + { + services.AddLocalization(); + + if (setupAction != null) + { + services.Configure(setupAction); + } + else + { + services.TryAddEnumerable( + ServiceDescriptor.Transient + , + MvcDataAnnotationsLocalizationOptionsSetup>()); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs new file mode 100644 index 0000000000..0248cee5c5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs @@ -0,0 +1,364 @@ +// Copyright (c) .NET 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.ComponentModel.DataAnnotations; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + /// + /// An implementation of and for + /// the System.ComponentModel.DataAnnotations attribute classes. + /// + public class DataAnnotationsMetadataProvider : + IBindingMetadataProvider, + IDisplayMetadataProvider, + IValidationMetadataProvider + { + private readonly IStringLocalizerFactory _stringLocalizerFactory; + private readonly MvcDataAnnotationsLocalizationOptions _localizationOptions; + + public DataAnnotationsMetadataProvider( + IOptions options, + IStringLocalizerFactory stringLocalizerFactory) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + _localizationOptions = options.Value; + _stringLocalizerFactory = stringLocalizerFactory; + } + + /// + public void CreateBindingMetadata(BindingMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var editableAttribute = context.Attributes.OfType().FirstOrDefault(); + if (editableAttribute != null) + { + context.BindingMetadata.IsReadOnly = !editableAttribute.AllowEdit; + } + } + + /// + public void CreateDisplayMetadata(DisplayMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var attributes = context.Attributes; + var dataTypeAttribute = attributes.OfType().FirstOrDefault(); + var displayAttribute = attributes.OfType().FirstOrDefault(); + var displayColumnAttribute = attributes.OfType().FirstOrDefault(); + var displayFormatAttribute = attributes.OfType().FirstOrDefault(); + var displayNameAttribute = attributes.OfType().FirstOrDefault(); + var hiddenInputAttribute = attributes.OfType().FirstOrDefault(); + var scaffoldColumnAttribute = attributes.OfType().FirstOrDefault(); + var uiHintAttribute = attributes.OfType().FirstOrDefault(); + + // Special case the [DisplayFormat] attribute hanging off an applied [DataType] attribute. This property is + // non-null for DataType.Currency, DataType.Date, DataType.Time, and potentially custom [DataType] + // subclasses. The DataType.Currency, DataType.Date, and DataType.Time [DisplayFormat] attributes have a + // non-null DataFormatString and the DataType.Date and DataType.Time [DisplayFormat] attributes have + // ApplyFormatInEditMode==true. + if (displayFormatAttribute == null && dataTypeAttribute != null) + { + displayFormatAttribute = dataTypeAttribute.DisplayFormat; + } + + var displayMetadata = context.DisplayMetadata; + + // ConvertEmptyStringToNull + if (displayFormatAttribute != null) + { + displayMetadata.ConvertEmptyStringToNull = displayFormatAttribute.ConvertEmptyStringToNull; + } + + // DataTypeName + if (dataTypeAttribute != null) + { + displayMetadata.DataTypeName = dataTypeAttribute.GetDataTypeName(); + } + else if (displayFormatAttribute != null && !displayFormatAttribute.HtmlEncode) + { + displayMetadata.DataTypeName = DataType.Html.ToString(); + } + + var containerType = context.Key.ContainerType ?? context.Key.ModelType; + IStringLocalizer localizer = null; + if (_stringLocalizerFactory != null && _localizationOptions.DataAnnotationLocalizerProvider != null) + { + localizer = _localizationOptions.DataAnnotationLocalizerProvider(containerType, _stringLocalizerFactory); + } + + // Description + if (displayAttribute != null) + { + if (localizer != null && + !string.IsNullOrEmpty(displayAttribute.Description) && + displayAttribute.ResourceType == null) + { + displayMetadata.Description = () => localizer[displayAttribute.Description]; + } + else + { + displayMetadata.Description = () => displayAttribute.GetDescription(); + } + } + + // DisplayFormatString + if (displayFormatAttribute != null) + { + displayMetadata.DisplayFormatString = displayFormatAttribute.DataFormatString; + } + + // DisplayName + // DisplayAttribute has precedence over DisplayNameAttribute. + if (displayAttribute?.GetName() != null) + { + if (localizer != null && + !string.IsNullOrEmpty(displayAttribute.Name) && + displayAttribute.ResourceType == null) + { + displayMetadata.DisplayName = () => localizer[displayAttribute.Name]; + } + else + { + displayMetadata.DisplayName = () => displayAttribute.GetName(); + } + } + else if (displayNameAttribute != null) + { + if (localizer != null && + !string.IsNullOrEmpty(displayNameAttribute.DisplayName)) + { + displayMetadata.DisplayName = () => localizer[displayNameAttribute.DisplayName]; + } + else + { + displayMetadata.DisplayName = () => displayNameAttribute.DisplayName; + } + } + + // EditFormatString + if (displayFormatAttribute != null && displayFormatAttribute.ApplyFormatInEditMode) + { + displayMetadata.EditFormatString = displayFormatAttribute.DataFormatString; + } + + // IsEnum et cetera + var underlyingType = Nullable.GetUnderlyingType(context.Key.ModelType) ?? context.Key.ModelType; + var underlyingTypeInfo = underlyingType.GetTypeInfo(); + + if (underlyingTypeInfo.IsEnum) + { + // IsEnum + displayMetadata.IsEnum = true; + + // IsFlagsEnum + displayMetadata.IsFlagsEnum = underlyingTypeInfo.IsDefined(typeof(FlagsAttribute), inherit: false); + + // EnumDisplayNamesAndValues and EnumNamesAndValues + // + // Order EnumDisplayNamesAndValues by DisplayAttribute.Order, then by the order of Enum.GetNames(). + // That method orders by absolute value, then its behavior is undefined (but hopefully stable). + // Add to EnumNamesAndValues in same order but Dictionary does not guarantee order will be preserved. + + var groupedDisplayNamesAndValues = new List>(); + var namesAndValues = new Dictionary(); + var enumLocalizer = _stringLocalizerFactory?.Create(underlyingType); + + var enumFields = Enum.GetNames(underlyingType) + .Select(name => underlyingType.GetField(name)) + .OrderBy(field => field.GetCustomAttribute(inherit: false)?.GetOrder() ?? 1000); + + foreach (var field in enumFields) + { + var groupName = GetDisplayGroup(field); + var value = ((Enum)field.GetValue(obj: null)).ToString("d"); + + groupedDisplayNamesAndValues.Add(new KeyValuePair( + new EnumGroupAndName( + groupName, + () => GetDisplayName(field, enumLocalizer)), + value)); + namesAndValues.Add(field.Name, value); + } + + displayMetadata.EnumGroupedDisplayNamesAndValues = groupedDisplayNamesAndValues; + displayMetadata.EnumNamesAndValues = namesAndValues; + } + + // HasNonDefaultEditFormat + if (!string.IsNullOrEmpty(displayFormatAttribute?.DataFormatString) && + displayFormatAttribute?.ApplyFormatInEditMode == true) + { + // Have a non-empty EditFormatString based on [DisplayFormat] from our cache. + if (dataTypeAttribute == null) + { + // Attributes include no [DataType]; [DisplayFormat] was applied directly. + displayMetadata.HasNonDefaultEditFormat = true; + } + else if (dataTypeAttribute.DisplayFormat != displayFormatAttribute) + { + // Attributes include separate [DataType] and [DisplayFormat]; [DisplayFormat] provided override. + displayMetadata.HasNonDefaultEditFormat = true; + } + else if (dataTypeAttribute.GetType() != typeof(DataTypeAttribute)) + { + // Attributes include [DisplayFormat] copied from [DataType] and [DataType] was of a subclass. + // Assume the [DataType] constructor used the protected DisplayFormat setter to override its + // default. That is derived [DataType] provided override. + displayMetadata.HasNonDefaultEditFormat = true; + } + } + + // HideSurroundingHtml + if (hiddenInputAttribute != null) + { + displayMetadata.HideSurroundingHtml = !hiddenInputAttribute.DisplayValue; + } + + // HtmlEncode + if (displayFormatAttribute != null) + { + displayMetadata.HtmlEncode = displayFormatAttribute.HtmlEncode; + } + + // NullDisplayText + if (displayFormatAttribute != null) + { + displayMetadata.NullDisplayText = displayFormatAttribute.NullDisplayText; + } + + // Order + if (displayAttribute?.GetOrder() != null) + { + displayMetadata.Order = displayAttribute.GetOrder().Value; + } + + // Placeholder + if (displayAttribute != null) + { + if (localizer != null && + !string.IsNullOrEmpty(displayAttribute.Prompt) && + displayAttribute.ResourceType == null) + { + displayMetadata.Placeholder = () => localizer[displayAttribute.Prompt]; + } + else + { + displayMetadata.Placeholder = () => displayAttribute.GetPrompt(); + } + } + + // ShowForDisplay + if (scaffoldColumnAttribute != null) + { + displayMetadata.ShowForDisplay = scaffoldColumnAttribute.Scaffold; + } + + // ShowForEdit + if (scaffoldColumnAttribute != null) + { + displayMetadata.ShowForEdit = scaffoldColumnAttribute.Scaffold; + } + + // SimpleDisplayProperty + if (displayColumnAttribute != null) + { + displayMetadata.SimpleDisplayProperty = displayColumnAttribute.DisplayColumn; + } + + // TemplateHint + if (uiHintAttribute != null) + { + displayMetadata.TemplateHint = uiHintAttribute.UIHint; + } + else if (hiddenInputAttribute != null) + { + displayMetadata.TemplateHint = "HiddenInput"; + } + } + + /// + public void CreateValidationMetadata(ValidationMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // 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(); + if (requiredAttribute != null) + { + context.ValidationMetadata.IsRequired = true; + } + + foreach (var attribute in context.Attributes.OfType()) + { + // If another provider has already added this attribute, do not repeat it. + // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and + // IClientModelValidator) to be added to the ValidationMetadata twice. + // This is to ensure we do not end up with duplication validation rules on the client side. + if (!context.ValidationMetadata.ValidatorMetadata.Contains(attribute)) + { + context.ValidationMetadata.ValidatorMetadata.Add(attribute); + } + } + } + + private static string GetDisplayName(FieldInfo field, IStringLocalizer stringLocalizer) + { + var display = field.GetCustomAttribute(inherit: false); + if (display != null) + { + // Note [Display(Name = "")] is allowed but we will not attempt to localize the empty name. + var name = display.GetName(); + if (stringLocalizer != null && !string.IsNullOrEmpty(name) && display.ResourceType == null) + { + name = stringLocalizer[name]; + } + + return name ?? field.Name; + } + + return field.Name; + } + + // Return non-empty group specified in a [Display] attribute for a field, if any; string.Empty otherwise. + private static string GetDisplayGroup(FieldInfo field) + { + var display = field.GetCustomAttribute(inherit: false); + if (display != null) + { + // Note [Display(Group = "")] is allowed. + var group = display.GetGroupName(); + if (group != null) + { + return group; + } + } + + return string.Empty; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidator.cs new file mode 100644 index 0000000000..563fa078a7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidator.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.ComponentModel.DataAnnotations; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + /// + /// Validates based on the given . + /// + public class DataAnnotationsModelValidator : IModelValidator + { + private static readonly object _emptyValidationContextInstance = new object(); + private readonly IStringLocalizer _stringLocalizer; + private readonly IValidationAttributeAdapterProvider _validationAttributeAdapterProvider; + + /// + /// Create a new instance of . + /// + /// The that defines what we're validating. + /// The used to create messages. + /// The + /// which 's will be created from. + public DataAnnotationsModelValidator( + IValidationAttributeAdapterProvider validationAttributeAdapterProvider, + ValidationAttribute attribute, + IStringLocalizer stringLocalizer) + { + if (validationAttributeAdapterProvider == null) + { + throw new ArgumentNullException(nameof(validationAttributeAdapterProvider)); + } + + if (attribute == null) + { + throw new ArgumentNullException(nameof(attribute)); + } + + _validationAttributeAdapterProvider = validationAttributeAdapterProvider; + Attribute = attribute; + _stringLocalizer = stringLocalizer; + } + + /// + /// The attribute being validated against. + /// + public ValidationAttribute Attribute { get; } + + /// + /// Validates the context against the . + /// + /// The context being validated. + /// An enumerable of the validation results. + public IEnumerable Validate(ModelValidationContext validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + if (validationContext.ModelMetadata == null) + { + throw new ArgumentException( + Resources.FormatPropertyOfTypeCannotBeNull( + nameof(validationContext.ModelMetadata), + typeof(ModelValidationContext)), + nameof(validationContext)); + } + if (validationContext.MetadataProvider == null) + { + throw new ArgumentException( + Resources.FormatPropertyOfTypeCannotBeNull( + nameof(validationContext.MetadataProvider), + typeof(ModelValidationContext)), + nameof(validationContext)); + } + + var metadata = validationContext.ModelMetadata; + var memberName = metadata.Name; + var container = validationContext.Container; + + var context = new ValidationContext( + instance: container ?? validationContext.Model ?? _emptyValidationContextInstance, + serviceProvider: validationContext.ActionContext?.HttpContext?.RequestServices, + items: null) + { + DisplayName = metadata.GetDisplayName(), + MemberName = memberName + }; + + var result = Attribute.GetValidationResult(validationContext.Model, context); + if (result != ValidationResult.Success) + { + string errorMessage; + if (_stringLocalizer != null && + !string.IsNullOrEmpty(Attribute.ErrorMessage) && + string.IsNullOrEmpty(Attribute.ErrorMessageResourceName) && + Attribute.ErrorMessageResourceType == null) + { + errorMessage = GetErrorMessage(validationContext) ?? result.ErrorMessage; + } + else + { + errorMessage = result.ErrorMessage; + } + + var validationResults = new List(); + if (result.MemberNames != null) + { + foreach (var resultMemberName in result.MemberNames) + { + // ModelValidationResult.MemberName is used by invoking validators (such as ModelValidator) to + // append construct the ModelKey for ModelStateDictionary. When validating at type level we + // want the returned MemberNames if specified (e.g. "person.Address.FirstName"). For property + // validation, the ModelKey can be constructed using the ModelMetadata and we should ignore + // MemberName (we don't want "person.Name.Name"). However the invoking validator does not have + // a way to distinguish between these two cases. Consequently we'll only set MemberName if this + // validation returns a MemberName that is different from the property being validated. + var newMemberName = string.Equals(resultMemberName, memberName, StringComparison.Ordinal) ? + null : + resultMemberName; + var validationResult = new ModelValidationResult(newMemberName, errorMessage); + + validationResults.Add(validationResult); + } + } + + if (validationResults.Count == 0) + { + // result.MemberNames was null or empty. + validationResults.Add(new ModelValidationResult(memberName: null, message: errorMessage)); + } + + return validationResults; + } + + return Enumerable.Empty(); + } + + private string GetErrorMessage(ModelValidationContextBase validationContext) + { + var adapter = _validationAttributeAdapterProvider.GetAttributeAdapter(Attribute, _stringLocalizer); + return adapter?.GetErrorMessage(validationContext); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidatorProvider.cs new file mode 100644 index 0000000000..5ece07f042 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidatorProvider.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; +using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + /// + /// An implementation of which provides validators + /// for attributes which derive from . It also provides + /// a validator for types which implement . + /// + public class DataAnnotationsModelValidatorProvider : IModelValidatorProvider + { + private readonly IOptions _options; + private readonly IStringLocalizerFactory _stringLocalizerFactory; + private readonly IValidationAttributeAdapterProvider _validationAttributeAdapterProvider; + + /// + /// Create a new instance of . + /// + /// The + /// that supplies s. + /// The . + /// The . + /// and + /// are nullable only for testing ease. + public DataAnnotationsModelValidatorProvider( + IValidationAttributeAdapterProvider validationAttributeAdapterProvider, + IOptions options, + IStringLocalizerFactory stringLocalizerFactory) + { + if (validationAttributeAdapterProvider == null) + { + throw new ArgumentNullException(nameof(validationAttributeAdapterProvider)); + } + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + _validationAttributeAdapterProvider = validationAttributeAdapterProvider; + _options = options; + _stringLocalizerFactory = stringLocalizerFactory; + } + + public void CreateValidators(ModelValidatorProviderContext context) + { + IStringLocalizer stringLocalizer = null; + if (_stringLocalizerFactory != null && _options.Value.DataAnnotationLocalizerProvider != null) + { + stringLocalizer = _options.Value.DataAnnotationLocalizerProvider( + context.ModelMetadata.ContainerType ?? context.ModelMetadata.ModelType, + _stringLocalizerFactory); + } + + for (var i = 0; i < context.Results.Count; i++) + { + var validatorItem = context.Results[i]; + if (validatorItem.Validator != null) + { + continue; + } + + var attribute = validatorItem.ValidatorMetadata as ValidationAttribute; + if (attribute == null) + { + continue; + } + + var validator = new DataAnnotationsModelValidator( + _validationAttributeAdapterProvider, + attribute, + stringLocalizer); + + validatorItem.Validator = validator; + validatorItem.IsReusable = true; + // Inserts validators based on whether or not they are 'required'. We want to run + // 'required' validators first so that we get the best possible error message. + if (attribute is RequiredAttribute) + { + context.Results.Remove(validatorItem); + context.Results.Insert(0, validatorItem); + } + } + + // Produce a validator if the type supports IValidatableObject + if (typeof(IValidatableObject).IsAssignableFrom(context.ModelMetadata.ModelType)) + { + context.Results.Add(new ValidatorItem + { + Validator = new ValidatableObjectAdapter(), + IsReusable = true + }); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataTypeAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataTypeAttributeAdapter.cs new file mode 100644 index 0000000000..b9bfa16165 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataTypeAttributeAdapter.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.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + /// + /// A validation adapter that is used to map 's to a single client side validation + /// rule. + /// + public class DataTypeAttributeAdapter : AttributeAdapterBase + { + public DataTypeAttributeAdapter(DataTypeAttribute attribute, string ruleName, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + if (string.IsNullOrEmpty(ruleName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(ruleName)); + } + + RuleName = ruleName; + } + + public string RuleName { get; } + + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, RuleName, GetErrorMessage(context)); + } + + /// + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName(), + Attribute.GetDataTypeName()); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DefaultClientModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DefaultClientModelValidatorProvider.cs new file mode 100644 index 0000000000..2fb0fe4769 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DefaultClientModelValidatorProvider.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET 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.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + /// + /// A default implementation of . + /// + /// + /// The provides validators from + /// instances in . + /// + public class DefaultClientModelValidatorProvider : IClientModelValidatorProvider + { + /// + public void CreateValidators(ClientValidatorProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // Perf: Avoid allocations + for (var i = 0; i < context.Results.Count; i++) + { + var validatorItem = context.Results[i]; + // Don't overwrite anything that was done by a previous provider. + if (validatorItem.Validator != null) + { + continue; + } + + var validator = validatorItem.ValidatorMetadata as IClientModelValidator; + if (validator != null) + { + validatorItem.Validator = validator; + validatorItem.IsReusable = true; + } + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/FileExtensionsAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/FileExtensionsAttributeAdapter.cs new file mode 100644 index 0000000000..827188f769 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/FileExtensionsAttributeAdapter.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 System.ComponentModel.DataAnnotations; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + public class FileExtensionsAttributeAdapter : AttributeAdapterBase + { + private readonly string _extensions; + private readonly string _formattedExtensions; + + public FileExtensionsAttributeAdapter(FileExtensionsAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + // Build the extension list based on how the JQuery Validation's 'extension' method expects it + // https://jqueryvalidation.org/extension-method/ + + // These lines follow the same approach as the FileExtensionsAttribute. + var normalizedExtensions = Attribute.Extensions.Replace(" ", string.Empty).Replace(".", string.Empty).ToLowerInvariant(); + var parsedExtensions = normalizedExtensions.Split(',').Select(e => "." + e); + _formattedExtensions = string.Join(", ", parsedExtensions); + _extensions = string.Join(",", parsedExtensions); + } + + /// + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-fileextensions", GetErrorMessage(context)); + MergeAttribute(context.Attributes, "data-val-fileextensions-extensions", _extensions); + } + + /// + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName(), + _formattedExtensions); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MaxLengthAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MaxLengthAttributeAdapter.cs new file mode 100644 index 0000000000..3a1a2cf9e9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MaxLengthAttributeAdapter.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.ComponentModel.DataAnnotations; +using System.Globalization; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + public class MaxLengthAttributeAdapter : AttributeAdapterBase + { + private readonly string _max; + + public MaxLengthAttributeAdapter(MaxLengthAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + _max = Attribute.Length.ToString(CultureInfo.InvariantCulture); + } + + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-maxlength", GetErrorMessage(context)); + MergeAttribute(context.Attributes, "data-val-maxlength-max", _max); + } + + /// + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName(), + Attribute.Length); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MinLengthAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MinLengthAttributeAdapter.cs new file mode 100644 index 0000000000..8d713b823c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MinLengthAttributeAdapter.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.ComponentModel.DataAnnotations; +using System.Globalization; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + public class MinLengthAttributeAdapter : AttributeAdapterBase + { + private readonly string _min; + + public MinLengthAttributeAdapter(MinLengthAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + _min = Attribute.Length.ToString(CultureInfo.InvariantCulture); + } + + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-minlength", GetErrorMessage(context)); + MergeAttribute(context.Attributes, "data-val-minlength-min", _min); + } + + /// + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName(), + Attribute.Length); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsLocalizationOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsLocalizationOptionsSetup.cs new file mode 100644 index 0000000000..e91f13b255 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsLocalizationOptionsSetup.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.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + /// + /// Sets up default options for . + /// + public class MvcDataAnnotationsLocalizationOptionsSetup : IConfigureOptions + { + /// + public void Configure(MvcDataAnnotationsLocalizationOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.DataAnnotationLocalizerProvider = (modelType, stringLocalizerFactory) => + stringLocalizerFactory.Create(modelType); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsMvcOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsMvcOptionsSetup.cs new file mode 100644 index 0000000000..1b94e5c157 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsMvcOptionsSetup.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; +using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + /// + /// Sets up default options for . + /// + public class MvcDataAnnotationsMvcOptionsSetup : IConfigureOptions + { + private readonly IStringLocalizerFactory _stringLocalizerFactory; + private readonly IValidationAttributeAdapterProvider _validationAttributeAdapterProvider; + private readonly IOptions _dataAnnotationLocalizationOptions; + + public MvcDataAnnotationsMvcOptionsSetup( + IValidationAttributeAdapterProvider validationAttributeAdapterProvider, + IOptions dataAnnotationLocalizationOptions) + { + if (validationAttributeAdapterProvider == null) + { + throw new ArgumentNullException(nameof(validationAttributeAdapterProvider)); + } + + if (dataAnnotationLocalizationOptions == null) + { + throw new ArgumentNullException(nameof(dataAnnotationLocalizationOptions)); + } + + _validationAttributeAdapterProvider = validationAttributeAdapterProvider; + _dataAnnotationLocalizationOptions = dataAnnotationLocalizationOptions; + } + + public MvcDataAnnotationsMvcOptionsSetup( + IValidationAttributeAdapterProvider validationAttributeAdapterProvider, + IOptions dataAnnotationLocalizationOptions, + IStringLocalizerFactory stringLocalizerFactory) + : this(validationAttributeAdapterProvider, dataAnnotationLocalizationOptions) + { + _stringLocalizerFactory = stringLocalizerFactory; + } + + public void Configure(MvcOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.ModelMetadataDetailsProviders.Add(new DataAnnotationsMetadataProvider( + _dataAnnotationLocalizationOptions, + _stringLocalizerFactory)); + + options.ModelValidatorProviders.Add(new DataAnnotationsModelValidatorProvider( + _validationAttributeAdapterProvider, + _dataAnnotationLocalizationOptions, + _stringLocalizerFactory)); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidator.cs new file mode 100644 index 0000000000..edc0286909 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidator.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; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + /// + /// An implementation of that provides the rule for validating + /// numeric types. + /// + public class NumericClientModelValidator : IClientModelValidator + { + /// + public void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-number", GetErrorMessage(context.ModelMetadata)); + } + + private static void MergeAttribute(IDictionary attributes, string key, string value) + { + if (!attributes.ContainsKey(key)) + { + attributes.Add(key, value); + } + } + + private string GetErrorMessage(ModelMetadata modelMetadata) + { + if (modelMetadata == null) + { + throw new ArgumentNullException(nameof(modelMetadata)); + } + + var messageProvider = modelMetadata.ModelBindingMessageProvider; + var name = modelMetadata.DisplayName ?? modelMetadata.Name; + if (name == null) + { + return messageProvider.NonPropertyValueMustBeANumberAccessor(); + } + + return messageProvider.ValueMustBeANumberAccessor(name); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidatorProvider.cs new file mode 100644 index 0000000000..a2e182d381 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidatorProvider.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 Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + /// + /// An implementation of which provides client validators + /// for specific numeric types. + /// + public class NumericClientModelValidatorProvider : IClientModelValidatorProvider + { + /// + public void CreateValidators(ClientValidatorProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var typeToValidate = context.ModelMetadata.UnderlyingOrModelType; + + // Check only the numeric types for which we set type='text'. + if (typeToValidate == typeof(float) || + typeToValidate == typeof(double) || + typeToValidate == typeof(decimal)) + { + for (var i = 0; i < context.Results.Count; i++) + { + var validator = context.Results[i].Validator; + if (validator != null && validator is NumericClientModelValidator) + { + // A validator is already present. No need to add one. + return; + } + } + + context.Results.Add(new ClientValidatorItem + { + Validator = new NumericClientModelValidator(), + IsReusable = true + }); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RangeAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RangeAttributeAdapter.cs new file mode 100644 index 0000000000..345e6cc3b9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RangeAttributeAdapter.cs @@ -0,0 +1,59 @@ +// Copyright (c) .NET 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.DataAnnotations; +using System.Globalization; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + public class RangeAttributeAdapter : AttributeAdapterBase + { + private readonly string _max; + private readonly string _min; + + public RangeAttributeAdapter(RangeAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + // This will trigger the conversion of Attribute.Minimum and Attribute.Maximum. + // This is needed, because the attribute is stateful and will convert from a string like + // "100m" to the decimal value 100. + // + // Validate a randomly selected number. + attribute.IsValid(3); + + _max = Convert.ToString(Attribute.Maximum, CultureInfo.InvariantCulture); + _min = Convert.ToString(Attribute.Minimum, CultureInfo.InvariantCulture); + } + + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-range", GetErrorMessage(context)); + MergeAttribute(context.Attributes, "data-val-range-max", _max); + MergeAttribute(context.Attributes, "data-val-range-min", _min); + } + + /// + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName(), + Attribute.Minimum, + Attribute.Maximum); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RegularExpressionAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RegularExpressionAttributeAdapter.cs new file mode 100644 index 0000000000..552c5dcad2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RegularExpressionAttributeAdapter.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 System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + public class RegularExpressionAttributeAdapter : AttributeAdapterBase + { + public RegularExpressionAttributeAdapter(RegularExpressionAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + } + + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-regex", GetErrorMessage(context)); + MergeAttribute(context.Attributes, "data-val-regex-pattern", Attribute.Pattern); + } + + /// + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName(), + Attribute.Pattern); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RequiredAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RequiredAttributeAdapter.cs new file mode 100644 index 0000000000..90e33ab49e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RequiredAttributeAdapter.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.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + public class RequiredAttributeAdapter : AttributeAdapterBase + { + public RequiredAttributeAdapter(RequiredAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + } + + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-required", GetErrorMessage(context)); + } + + /// + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + return GetErrorMessage(validationContext.ModelMetadata, validationContext.ModelMetadata.GetDisplayName()); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/StringLengthAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/StringLengthAttributeAdapter.cs new file mode 100644 index 0000000000..f733d4470a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/StringLengthAttributeAdapter.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.ComponentModel.DataAnnotations; +using System.Globalization; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + public class StringLengthAttributeAdapter : AttributeAdapterBase + { + private readonly string _max; + private readonly string _min; + + public StringLengthAttributeAdapter(StringLengthAttribute attribute, IStringLocalizer stringLocalizer) + : base(attribute, stringLocalizer) + { + _max = Attribute.MaximumLength.ToString(CultureInfo.InvariantCulture); + _min = Attribute.MinimumLength.ToString(CultureInfo.InvariantCulture); + } + + /// + public override void AddValidation(ClientModelValidationContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + MergeAttribute(context.Attributes, "data-val", "true"); + MergeAttribute(context.Attributes, "data-val-length", GetErrorMessage(context)); + + if (Attribute.MaximumLength != int.MaxValue) + { + MergeAttribute(context.Attributes, "data-val-length-max", _max); + } + + if (Attribute.MinimumLength != 0) + { + MergeAttribute(context.Attributes, "data-val-length-min", _min); + } + } + + /// + public override string GetErrorMessage(ModelValidationContextBase validationContext) + { + if (validationContext == null) + { + throw new ArgumentNullException(nameof(validationContext)); + } + + return GetErrorMessage( + validationContext.ModelMetadata, + validationContext.ModelMetadata.GetDisplayName(), + Attribute.MaximumLength, + Attribute.MinimumLength); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/ValidatableObjectAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/ValidatableObjectAdapter.cs new file mode 100644 index 0000000000..55a9ca23a1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/ValidatableObjectAdapter.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.ComponentModel.DataAnnotations; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +{ + public class ValidatableObjectAdapter : IModelValidator + { + public IEnumerable Validate(ModelValidationContext context) + { + var model = context.Model; + if (model == null) + { + return Enumerable.Empty(); + } + + if (!(model is IValidatableObject validatable)) + { + var message = Resources.FormatValidatableObjectAdapter_IncompatibleType( + typeof(IValidatableObject).Name, + model.GetType()); + + throw new InvalidOperationException(message); + } + + // The constructed ValidationContext is intentionally slightly different from what + // DataAnnotationsModelValidator creates. The instance parameter would be context.Container + // (if non-null) in that class. But, DataAnnotationsModelValidator _also_ passes context.Model + // separately to any ValidationAttribute. + var validationContext = new ValidationContext( + instance: validatable, + serviceProvider: context.ActionContext?.HttpContext?.RequestServices, + items: null) + { + DisplayName = context.ModelMetadata.GetDisplayName(), + MemberName = context.ModelMetadata.Name, + }; + + return ConvertResults(validatable.Validate(validationContext)); + } + + private IEnumerable ConvertResults(IEnumerable results) + { + foreach (var result in results) + { + if (result != ValidationResult.Success) + { + if (result.MemberNames == null || !result.MemberNames.Any()) + { + yield return new ModelValidationResult(memberName: null, message: result.ErrorMessage); + } + else + { + foreach (var memberName in result.MemberNames) + { + yield return new ModelValidationResult(memberName, result.ErrorMessage); + } + } + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj new file mode 100644 index 0000000000..336474aae0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj @@ -0,0 +1,19 @@ + + + + ASP.NET Core MVC metadata and validation system using System.ComponentModel.DataAnnotations. + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;aspnetcoremvc + + + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs new file mode 100644 index 0000000000..99d8742296 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.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; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations +{ + /// + /// Provides programmatic configuration for DataAnnotations localization in the MVC framework. + /// + public class MvcDataAnnotationsLocalizationOptions + { + /// + /// The delegate to invoke for creating . + /// + public Func DataAnnotationLocalizerProvider; + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..c375950300 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/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.DataAnnotations.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..30ce539240 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/Resources.Designer.cs @@ -0,0 +1,72 @@ +// +namespace Microsoft.AspNetCore.Mvc.DataAnnotations +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Mvc.DataAnnotations.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// The model object inside the metadata claimed to be compatible with '{0}', but was actually '{1}'. + /// + internal static string ValidatableObjectAdapter_IncompatibleType + { + get => GetString("ValidatableObjectAdapter_IncompatibleType"); + } + + /// + /// The model object inside the metadata claimed to be compatible with '{0}', but was actually '{1}'. + /// + internal static string FormatValidatableObjectAdapter_IncompatibleType(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ValidatableObjectAdapter_IncompatibleType"), p0, p1); + + /// + /// Value cannot be null or empty. + /// + internal static string ArgumentCannotBeNullOrEmpty + { + get => GetString("ArgumentCannotBeNullOrEmpty"); + } + + /// + /// Value cannot be null or empty. + /// + internal static string FormatArgumentCannotBeNullOrEmpty() + => GetString("ArgumentCannotBeNullOrEmpty"); + + /// + /// The '{0}' property of '{1}' must not be null. + /// + internal static string PropertyOfTypeCannotBeNull + { + get => GetString("PropertyOfTypeCannotBeNull"); + } + + /// + /// The '{0}' property of '{1}' must not be null. + /// + internal static string FormatPropertyOfTypeCannotBeNull(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("PropertyOfTypeCannotBeNull"), 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/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Resources.resx new file mode 100644 index 0000000000..1c0fc25774 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Resources.resx @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 model object inside the metadata claimed to be compatible with '{0}', but was actually '{1}'. + + + Value cannot be null or empty. + + + The '{0}' property of '{1}' must not be null. + + \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterOfTAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterOfTAttribute.cs new file mode 100644 index 0000000000..9a2d284098 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterOfTAttribute.cs @@ -0,0 +1,85 @@ +// Copyright (c) .NET 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 Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations +{ + /// + /// An implementation of which understands data annotation attributes. + /// + /// The type of the attribute. + public abstract class ValidationAttributeAdapter : IClientModelValidator + where TAttribute : ValidationAttribute + { + private readonly IStringLocalizer _stringLocalizer; + /// + /// Create a new instance of . + /// + /// The instance to validate. + /// The . + public ValidationAttributeAdapter(TAttribute attribute, IStringLocalizer stringLocalizer) + { + Attribute = attribute; + _stringLocalizer = stringLocalizer; + } + + /// + /// Gets the instance. + /// + public TAttribute Attribute { get; } + + /// + public abstract void AddValidation(ClientModelValidationContext context); + + /// + /// Adds the given and into + /// if does not contain a value for + /// . + /// + /// The HTML attributes dictionary. + /// The attribute key. + /// The attribute value. + /// true if an attribute was added, otherwise false. + protected static bool MergeAttribute(IDictionary attributes, string key, string value) + { + if (attributes.ContainsKey(key)) + { + return false; + } + + attributes.Add(key, value); + return true; + } + + /// + /// Gets the error message formatted using the . + /// + /// The associated with the model annotated with + /// . + /// The value arguments which will be used in constructing the error message. + /// Formatted error string. + protected virtual string GetErrorMessage(ModelMetadata modelMetadata, params object[] arguments) + { + if (modelMetadata == null) + { + throw new ArgumentNullException(nameof(modelMetadata)); + } + + if (_stringLocalizer != null && + !string.IsNullOrEmpty(Attribute.ErrorMessage) && + string.IsNullOrEmpty(Attribute.ErrorMessageResourceName) && + Attribute.ErrorMessageResourceType == null) + { + return _stringLocalizer[Attribute.ErrorMessage, arguments]; + } + + return Attribute.FormatErrorMessage(modelMetadata.GetDisplayName()); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterProvider.cs new file mode 100644 index 0000000000..a16b405a5f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterProvider.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.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations +{ + /// + /// Creates an for the given attribute. + /// + public class ValidationAttributeAdapterProvider : IValidationAttributeAdapterProvider + { + /// + /// Creates an for the given attribute. + /// + /// The attribute to create an adapter for. + /// The localizer to provide to the adapter. + /// An for the given attribute. + public IAttributeAdapter GetAttributeAdapter(ValidationAttribute attribute, IStringLocalizer stringLocalizer) + { + if (attribute == null) + { + throw new ArgumentNullException(nameof(attribute)); + } + + IAttributeAdapter adapter; + + var type = attribute.GetType(); + + if (type == typeof(RegularExpressionAttribute)) + { + adapter = new RegularExpressionAttributeAdapter((RegularExpressionAttribute)attribute, stringLocalizer); + } + else if (type == typeof(MaxLengthAttribute)) + { + adapter = new MaxLengthAttributeAdapter((MaxLengthAttribute)attribute, stringLocalizer); + } + else if (type == typeof(RequiredAttribute)) + { + adapter = new RequiredAttributeAdapter((RequiredAttribute)attribute, stringLocalizer); + } + else if (type == typeof(CompareAttribute)) + { + adapter = new CompareAttributeAdapter((CompareAttribute)attribute, stringLocalizer); + } + else if (type == typeof(MinLengthAttribute)) + { + adapter = new MinLengthAttributeAdapter((MinLengthAttribute)attribute, stringLocalizer); + } + else if (type == typeof(CreditCardAttribute)) + { + adapter = new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-creditcard", stringLocalizer); + } + else if (type == typeof(StringLengthAttribute)) + { + adapter = new StringLengthAttributeAdapter((StringLengthAttribute)attribute, stringLocalizer); + } + else if (type == typeof(RangeAttribute)) + { + adapter = new RangeAttributeAdapter((RangeAttribute)attribute, stringLocalizer); + } + else if (type == typeof(EmailAddressAttribute)) + { + adapter = new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-email", stringLocalizer); + } + else if (type == typeof(PhoneAttribute)) + { + adapter = new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-phone", stringLocalizer); + } + else if (type == typeof(UrlAttribute)) + { + adapter = new DataTypeAttributeAdapter((DataTypeAttribute)attribute, "data-val-url", stringLocalizer); + } + else if (type == typeof(FileExtensionsAttribute)) + { + adapter = new FileExtensionsAttributeAdapter((FileExtensionsAttribute)attribute, stringLocalizer); + } + else + { + adapter = null; + } + + return adapter; + } + }; +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/baseline.netcore.json new file mode 100644 index 0000000000..6ef863e5b3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/baseline.netcore.json @@ -0,0 +1,417 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.DataAnnotations, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.Mvc.HiddenInputAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_DisplayValue", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DisplayValue", + "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.DataAnnotations.AttributeAdapterBase", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "Microsoft.AspNetCore.Mvc.DataAnnotations.ValidationAttributeAdapter", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetErrorMessage", + "Parameters": [ + { + "Name": "validationContext", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "Abstract": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "attribute", + "Type": "T0" + }, + { + "Name": "stringLocalizer", + "Type": "Microsoft.Extensions.Localization.IStringLocalizer" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TAttribute", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "System.ComponentModel.DataAnnotations.ValidationAttribute" + ] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetErrorMessage", + "Parameters": [ + { + "Name": "validationContext", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase" + } + ], + "ReturnType": "System.String", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.IValidationAttributeAdapterProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetAttributeAdapter", + "Parameters": [ + { + "Name": "attribute", + "Type": "System.ComponentModel.DataAnnotations.ValidationAttribute" + }, + { + "Name": "stringLocalizer", + "Type": "Microsoft.Extensions.Localization.IStringLocalizer" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.MvcDataAnnotationsLocalizationOptions", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "DataAnnotationLocalizerProvider", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.ValidationAttributeAdapter", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Attribute", + "Parameters": [], + "ReturnType": "T0", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddValidation", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MergeAttribute", + "Parameters": [ + { + "Name": "attributes", + "Type": "System.Collections.Generic.IDictionary" + }, + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetErrorMessage", + "Parameters": [ + { + "Name": "modelMetadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "System.String", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "attribute", + "Type": "T0" + }, + { + "Name": "stringLocalizer", + "Type": "Microsoft.Extensions.Localization.IStringLocalizer" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TAttribute", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "System.ComponentModel.DataAnnotations.ValidationAttribute" + ] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.ValidationAttributeAdapterProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.DataAnnotations.IValidationAttributeAdapterProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetAttributeAdapter", + "Parameters": [ + { + "Name": "attribute", + "Type": "System.ComponentModel.DataAnnotations.ValidationAttribute" + }, + { + "Name": "stringLocalizer", + "Type": "Microsoft.Extensions.Localization.IStringLocalizer" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.DataAnnotations.IValidationAttributeAdapterProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcDataAnnotationsMvcBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddDataAnnotationsLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddDataAnnotationsLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcDataAnnotationsMvcCoreBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddDataAnnotations", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddDataAnnotationsLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddDataAnnotationsLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcBuilderExtensions.cs new file mode 100644 index 0000000000..93e7bc7e40 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcBuilderExtensions.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; +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extensions methods for configuring MVC via an . + /// + public static class MvcJsonMvcBuilderExtensions + { + /// + /// Adds configuration of for the application. + /// + /// The . + /// The which need to be configured. + public static IMvcBuilder AddJsonOptions( + 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/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs new file mode 100644 index 0000000000..accd9045eb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs @@ -0,0 +1,85 @@ +// Copyright (c) .NET 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.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Formatters.Json; +using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using Newtonsoft.Json; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class MvcJsonMvcCoreBuilderExtensions + { + public static IMvcCoreBuilder AddJsonFormatters(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddJsonFormatterServices(builder.Services); + return builder; + } + + public static IMvcCoreBuilder AddJsonFormatters( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + AddJsonFormatterServices(builder.Services); + + builder.Services.Configure((options) => setupAction(options.SerializerSettings)); + + return builder; + } + + /// + /// Adds configuration of for the application. + /// + /// The . + /// The which need to be configured. + /// The . + public static IMvcCoreBuilder AddJsonOptions( + 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; + } + + // Internal for testing. + internal static void AddJsonFormatterServices(IServiceCollection services) + { + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcJsonMvcOptionsSetup>()); + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcJsonOptionsConfigureCompatibilityOptions>()); + services.TryAddEnumerable( + ServiceDescriptor.Transient()); + services.TryAddSingleton(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonArrayPool.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonArrayPool.cs new file mode 100644 index 0000000000..437f9bcf98 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonArrayPool.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; +using System.Buffers; +using Newtonsoft.Json; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal +{ + public class JsonArrayPool : IArrayPool + { + private readonly ArrayPool _inner; + + public JsonArrayPool(ArrayPool inner) + { + if (inner == null) + { + throw new ArgumentNullException(nameof(inner)); + } + + _inner = inner; + } + + public T[] Rent(int minimumLength) + { + return _inner.Rent(minimumLength); + } + + public void Return(T[] array) + { + if (array == null) + { + throw new ArgumentNullException(nameof(array)); + } + + _inner.Return(array); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs new file mode 100644 index 0000000000..f7e2fc09f2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs @@ -0,0 +1,136 @@ +// Copyright (c) .NET 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.Buffers; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal +{ + /// + /// Executes a to write to the response. + /// + public class JsonResultExecutor + { + private static readonly string DefaultContentType = new MediaTypeHeaderValue("application/json") + { + Encoding = Encoding.UTF8 + }.ToString(); + + private readonly IArrayPool _charPool; + + /// + /// Creates a new . + /// + /// The . + /// The . + /// The . + /// The for creating buffers. + public JsonResultExecutor( + IHttpResponseStreamWriterFactory writerFactory, + ILogger logger, + IOptions options, + ArrayPool charPool) + { + if (writerFactory == null) + { + throw new ArgumentNullException(nameof(writerFactory)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (charPool == null) + { + throw new ArgumentNullException(nameof(charPool)); + } + + WriterFactory = writerFactory; + Logger = logger; + Options = options.Value; + _charPool = new JsonArrayPool(charPool); + } + + /// + /// Gets the . + /// + protected ILogger Logger { get; } + + /// + /// Gets the . + /// + protected MvcJsonOptions Options { get; } + + /// + /// Gets the . + /// + protected IHttpResponseStreamWriterFactory WriterFactory { get; } + + /// + /// Executes the and writes the response. + /// + /// The . + /// The . + /// A which will complete when writing has completed. + public virtual Task ExecuteAsync(ActionContext context, JsonResult result) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + var response = context.HttpContext.Response; + + ResponseContentTypeHelper.ResolveContentTypeAndEncoding( + result.ContentType, + response.ContentType, + DefaultContentType, + out var resolvedContentType, + out var resolvedContentTypeEncoding); + + response.ContentType = resolvedContentType; + + if (result.StatusCode != null) + { + response.StatusCode = result.StatusCode.Value; + } + + var serializerSettings = result.SerializerSettings ?? Options.SerializerSettings; + + Logger.JsonResultExecuting(result.Value); + using (var writer = WriterFactory.CreateWriter(response.Body, resolvedContentTypeEncoding)) + { + using (var jsonWriter = new JsonTextWriter(writer)) + { + jsonWriter.ArrayPool = _charPool; + jsonWriter.CloseOutput = false; + jsonWriter.AutoCompleteOnClose = false; + + var jsonSerializer = JsonSerializer.Create(serializerSettings); + jsonSerializer.Serialize(jsonWriter, result.Value); + } + } + + return Task.CompletedTask; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonSerializerObjectPolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonSerializerObjectPolicy.cs new file mode 100644 index 0000000000..5cd0a0e3d6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonSerializerObjectPolicy.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 Microsoft.Extensions.ObjectPool; +using Newtonsoft.Json; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal +{ + /// + /// for . + /// + public class JsonSerializerObjectPolicy : IPooledObjectPolicy + { + private readonly JsonSerializerSettings _serializerSettings; + + /// + /// Initializes a new instance of . + /// + /// The used to instantiate + /// instances. + public JsonSerializerObjectPolicy(JsonSerializerSettings serializerSettings) + { + _serializerSettings = serializerSettings; + } + + /// + public JsonSerializer Create() => JsonSerializer.Create(_serializerSettings); + + /// + public bool Return(JsonSerializer serializer) => true; + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MediaTypeHeaderValues.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MediaTypeHeaderValues.cs new file mode 100644 index 0000000000..d9fb986507 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MediaTypeHeaderValues.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 Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal +{ + internal class MediaTypeHeaderValues + { + public static readonly MediaTypeHeaderValue ApplicationJson + = MediaTypeHeaderValue.Parse("application/json").CopyAsReadOnly(); + + public static readonly MediaTypeHeaderValue TextJson + = MediaTypeHeaderValue.Parse("text/json").CopyAsReadOnly(); + + public static readonly MediaTypeHeaderValue ApplicationJsonPatch + = MediaTypeHeaderValue.Parse("application/json-patch+json").CopyAsReadOnly(); + + public static readonly MediaTypeHeaderValue ApplicationAnyJsonSyntax + = MediaTypeHeaderValue.Parse("application/*+json").CopyAsReadOnly(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonLoggerExtensions.cs new file mode 100644 index 0000000000..c06d819865 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonLoggerExtensions.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; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal +{ + internal static class MvcJsonLoggerExtensions + { + private static readonly Action _jsonInputFormatterCrashed; + + private static readonly Action _jsonResultExecuting; + + static MvcJsonLoggerExtensions() + { + _jsonInputFormatterCrashed = LoggerMessage.Define( + LogLevel.Debug, + 1, + "JSON input formatter threw an exception."); + + _jsonResultExecuting = LoggerMessage.Define( + LogLevel.Information, + 1, + "Executing JsonResult, writing value of type '{Type}'."); + } + + public static void JsonInputException(this ILogger logger, Exception exception) + { + _jsonInputFormatterCrashed(logger, exception); + } + + public static void JsonResultExecuting(this ILogger logger, object value) + { + var type = value == null ? "null" : value.GetType().FullName; + _jsonResultExecuting(logger, type, null); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs new file mode 100644 index 0000000000..33f2b51eac --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.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.Buffers; +using Microsoft.AspNetCore.JsonPatch; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; +using Newtonsoft.Json.Linq; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal +{ + /// + /// Sets up JSON formatter options for . + /// + public class MvcJsonMvcOptionsSetup : IConfigureOptions + { + private readonly ILoggerFactory _loggerFactory; + private readonly MvcJsonOptions _jsonOptions; + private readonly ArrayPool _charPool; + private readonly ObjectPoolProvider _objectPoolProvider; + + public MvcJsonMvcOptionsSetup( + ILoggerFactory loggerFactory, + IOptions jsonOptions, + ArrayPool charPool, + ObjectPoolProvider objectPoolProvider) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + if (jsonOptions == null) + { + throw new ArgumentNullException(nameof(jsonOptions)); + } + + if (charPool == null) + { + throw new ArgumentNullException(nameof(charPool)); + } + + if (objectPoolProvider == null) + { + throw new ArgumentNullException(nameof(objectPoolProvider)); + } + + _loggerFactory = loggerFactory; + _jsonOptions = jsonOptions.Value; + _charPool = charPool; + _objectPoolProvider = objectPoolProvider; + } + + public void Configure(MvcOptions options) + { + options.OutputFormatters.Add(new JsonOutputFormatter(_jsonOptions.SerializerSettings, _charPool)); + + // Register JsonPatchInputFormatter before JsonInputFormatter, otherwise + // JsonInputFormatter would consume "application/json-patch+json" requests + // before JsonPatchInputFormatter gets to see them. + var jsonInputPatchLogger = _loggerFactory.CreateLogger(); + options.InputFormatters.Add(new JsonPatchInputFormatter( + jsonInputPatchLogger, + _jsonOptions.SerializerSettings, + _charPool, + _objectPoolProvider, + options, + _jsonOptions)); + + var jsonInputLogger = _loggerFactory.CreateLogger(); + options.InputFormatters.Add(new JsonInputFormatter( + jsonInputLogger, + _jsonOptions.SerializerSettings, + _charPool, + _objectPoolProvider, + options, + _jsonOptions)); + + options.FormatterMappings.SetMediaTypeMappingForFormat("json", MediaTypeHeaderValues.ApplicationJson); + + options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IJsonPatchDocument))); + options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(JToken))); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs new file mode 100644 index 0000000000..cc21792d71 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs @@ -0,0 +1,438 @@ +// Copyright (c) .NET 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.Buffers; +using System.Diagnostics; +using System.IO; +using System.Runtime.ExceptionServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; +using Newtonsoft.Json; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// A for JSON content. + /// + public class JsonInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy + { + private readonly IArrayPool _charPool; + private readonly ILogger _logger; + private readonly ObjectPoolProvider _objectPoolProvider; + private readonly MvcOptions _options; + private readonly MvcJsonOptions _jsonOptions; + + // These fields are used when one of the legacy constructors is called that doesn't provide the MvcOptions or + // MvcJsonOptions. + private readonly bool _suppressInputFormatterBuffering; + private readonly bool _allowInputFormatterExceptionMessages; + + private ObjectPool _jsonSerializerPool; + + /// + /// Initializes a new instance of . + /// + /// The . + /// + /// The . Should be either the application-wide settings + /// () or an instance + /// initially returned. + /// + /// The . + /// The . + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public JsonInputFormatter( + ILogger logger, + JsonSerializerSettings serializerSettings, + ArrayPool charPool, + ObjectPoolProvider objectPoolProvider) : + this(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering: false) + { + // This constructor by default buffers the request body as its the most secure setting + } + + /// + /// Initializes a new instance of . + /// + /// The . + /// + /// The . Should be either the application-wide settings + /// () or an instance + /// initially returned. + /// + /// The . + /// The . + /// Flag to buffer entire request body before deserializing it. + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public JsonInputFormatter( + ILogger logger, + JsonSerializerSettings serializerSettings, + ArrayPool charPool, + ObjectPoolProvider objectPoolProvider, + bool suppressInputFormatterBuffering) + : this(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, allowInputFormatterExceptionMessages: false) + { + // This constructor by default treats JSON deserialization exceptions as unsafe + // because this is the default in 2.0 + } + + /// + /// Initializes a new instance of . + /// + /// The . + /// + /// The . Should be either the application-wide settings + /// () or an instance + /// initially returned. + /// + /// The . + /// The . + /// Flag to buffer entire request body before deserializing it. + /// If , JSON deserialization exception messages will replaced by a generic message in model state. + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public JsonInputFormatter( + ILogger logger, + JsonSerializerSettings serializerSettings, + ArrayPool charPool, + ObjectPoolProvider objectPoolProvider, + bool suppressInputFormatterBuffering, + bool allowInputFormatterExceptionMessages) + { + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + if (serializerSettings == null) + { + throw new ArgumentNullException(nameof(serializerSettings)); + } + + if (charPool == null) + { + throw new ArgumentNullException(nameof(charPool)); + } + + if (objectPoolProvider == null) + { + throw new ArgumentNullException(nameof(objectPoolProvider)); + } + + _logger = logger; + SerializerSettings = serializerSettings; + _charPool = new JsonArrayPool(charPool); + _objectPoolProvider = objectPoolProvider; + _suppressInputFormatterBuffering = suppressInputFormatterBuffering; + _allowInputFormatterExceptionMessages = allowInputFormatterExceptionMessages; + + SupportedEncodings.Add(UTF8EncodingWithoutBOM); + SupportedEncodings.Add(UTF16EncodingLittleEndian); + + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson); + SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson); + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax); + } + + /// + /// Initializes a new instance of . + /// + /// The . + /// + /// The . Should be either the application-wide settings + /// () or an instance + /// initially returned. + /// + /// The . + /// The . + /// The . + /// The . + public JsonInputFormatter( + ILogger logger, + JsonSerializerSettings serializerSettings, + ArrayPool charPool, + ObjectPoolProvider objectPoolProvider, + MvcOptions options, + MvcJsonOptions jsonOptions) + { + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + if (serializerSettings == null) + { + throw new ArgumentNullException(nameof(serializerSettings)); + } + + if (charPool == null) + { + throw new ArgumentNullException(nameof(charPool)); + } + + if (objectPoolProvider == null) + { + throw new ArgumentNullException(nameof(objectPoolProvider)); + } + + _logger = logger; + SerializerSettings = serializerSettings; + _charPool = new JsonArrayPool(charPool); + _objectPoolProvider = objectPoolProvider; + _options = options; + _jsonOptions = jsonOptions; + + SupportedEncodings.Add(UTF8EncodingWithoutBOM); + SupportedEncodings.Add(UTF16EncodingLittleEndian); + + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson); + SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson); + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax); + } + + /// + public virtual InputFormatterExceptionPolicy ExceptionPolicy + { + get + { + if (GetType() == typeof(JsonInputFormatter)) + { + return InputFormatterExceptionPolicy.MalformedInputExceptions; + } + return InputFormatterExceptionPolicy.AllExceptions; + } + } + + /// + /// Gets the used to configure the . + /// + /// + /// Any modifications to the object after this + /// has been used will have no effect. + /// + protected JsonSerializerSettings SerializerSettings { get; } + + /// + public override async Task ReadRequestBodyAsync( + InputFormatterContext context, + Encoding encoding) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + var request = context.HttpContext.Request; + + var suppressInputFormatterBuffering = _options?.SuppressInputFormatterBuffering ?? _suppressInputFormatterBuffering; + + if (!request.Body.CanSeek && !suppressInputFormatterBuffering) + { + // JSON.Net does synchronous reads. In order to avoid blocking on the stream, we asynchronously + // read everything into a buffer, and then seek back to the beginning. + request.EnableBuffering(); + Debug.Assert(request.Body.CanSeek); + + await request.Body.DrainAsync(CancellationToken.None); + request.Body.Seek(0L, SeekOrigin.Begin); + } + + using (var streamReader = context.ReaderFactory(request.Body, encoding)) + { + using (var jsonReader = new JsonTextReader(streamReader)) + { + jsonReader.ArrayPool = _charPool; + jsonReader.CloseInput = false; + + var successful = true; + Exception exception = null; + void ErrorHandler(object sender, Newtonsoft.Json.Serialization.ErrorEventArgs eventArgs) + { + successful = false; + + // Handle path combinations such as "" + "Property", "Parent" + "Property", or "Parent" + "[12]". + var key = eventArgs.ErrorContext.Path; + if (!string.IsNullOrEmpty(context.ModelName)) + { + if (string.IsNullOrEmpty(eventArgs.ErrorContext.Path)) + { + key = context.ModelName; + } + else if (eventArgs.ErrorContext.Path[0] == '[') + { + key = context.ModelName + eventArgs.ErrorContext.Path; + } + else + { + key = context.ModelName + "." + eventArgs.ErrorContext.Path; + } + } + + var metadata = GetPathMetadata(context.Metadata, eventArgs.ErrorContext.Path); + var modelStateException = WrapExceptionForModelState(eventArgs.ErrorContext.Error); + context.ModelState.TryAddModelError(key, modelStateException, metadata); + + _logger.JsonInputException(eventArgs.ErrorContext.Error); + + exception = eventArgs.ErrorContext.Error; + + // Error must always be marked as handled + // Failure to do so can cause the exception to be rethrown at every recursive level and + // overflow the stack for x64 CLR processes + eventArgs.ErrorContext.Handled = true; + } + + var type = context.ModelType; + var jsonSerializer = CreateJsonSerializer(); + jsonSerializer.Error += ErrorHandler; + object model; + try + { + model = jsonSerializer.Deserialize(jsonReader, type); + } + finally + { + // Clean up the error handler since CreateJsonSerializer() pools instances. + jsonSerializer.Error -= ErrorHandler; + ReleaseJsonSerializer(jsonSerializer); + } + + if (successful) + { + if (model == null && !context.TreatEmptyInputAsDefaultValue) + { + // Some nonempty inputs might deserialize as null, for example whitespace, + // or the JSON-encoded value "null". The upstream BodyModelBinder needs to + // be notified that we don't regard this as a real input so it can register + // a model binding error. + return InputFormatterResult.NoValue(); + } + else + { + return InputFormatterResult.Success(model); + } + } + + if (!(exception is JsonException || exception is OverflowException)) + { + var exceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); + exceptionDispatchInfo.Throw(); + } + + return InputFormatterResult.Failure(); + } + } + } + + /// + /// Called during deserialization to get the . + /// + /// The used during deserialization. + /// + /// This method works in tandem with to + /// manage the lifetimes of instances. + /// + protected virtual JsonSerializer CreateJsonSerializer() + { + if (_jsonSerializerPool == null) + { + _jsonSerializerPool = _objectPoolProvider.Create(new JsonSerializerObjectPolicy(SerializerSettings)); + } + + return _jsonSerializerPool.Get(); + } + + /// + /// Releases the instance. + /// + /// The to release. + /// + /// This method works in tandem with to + /// manage the lifetimes of instances. + /// + protected virtual void ReleaseJsonSerializer(JsonSerializer serializer) + => _jsonSerializerPool.Return(serializer); + + private ModelMetadata GetPathMetadata(ModelMetadata metadata, string path) + { + var index = 0; + while (index >= 0 && index < path.Length) + { + if (path[index] == '[') + { + // At start of "[0]". + if (metadata.ElementMetadata == null) + { + // Odd case but don't throw just because ErrorContext had an odd-looking path. + break; + } + + metadata = metadata.ElementMetadata; + index = path.IndexOf(']', index); + } + else if (path[index] == '.' || path[index] == ']') + { + // Skip '.' in "prefix.property" or "[0].property" or ']' in "[0]". + index++; + } + else + { + // At start of "property", "property." or "property[0]". + var endIndex = path.IndexOfAny(new[] { '.', '[' }, index); + if (endIndex == -1) + { + endIndex = path.Length; + } + + var propertyName = path.Substring(index, endIndex - index); + if (metadata.Properties[propertyName] == null) + { + // Odd case but don't throw just because ErrorContext had an odd-looking path. + break; + } + + metadata = metadata.Properties[propertyName]; + index = endIndex; + } + } + + return metadata; + } + + private Exception WrapExceptionForModelState(Exception exception) + { + // In 2.0 and earlier we always gave a generic error message for errors that come from JSON.NET + // We only allow it in 2.1 and newer if the app opts-in. + if (!(_jsonOptions?.AllowInputFormatterExceptionMessages ?? _allowInputFormatterExceptionMessages)) + { + // This app is not opted-in to JSON.NET messages, return the original exception. + return exception; + } + + // It's not known that Json.NET currently ever raises error events with exceptions + // other than these two types, but we're being conservative and limiting which ones + // we regard as having safe messages to expose to clients + if (exception is JsonReaderException || exception is JsonSerializationException) + { + // InputFormatterException specifies that the message is safe to return to a client, it will + // be added to model state. + return new InputFormatterException(exception.Message, exception); + } + + // Not a known exception type, so we're not going to assume that it's safe. + return exception; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs new file mode 100644 index 0000000000..744d558680 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs @@ -0,0 +1,157 @@ +// Copyright (c) .NET 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.Buffers; +using System.ComponentModel; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; +using Newtonsoft.Json; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// A for JSON content. + /// + public class JsonOutputFormatter : TextOutputFormatter + { + private readonly IArrayPool _charPool; + + // Perf: JsonSerializers are relatively expensive to create, and are thread safe. We cache + // the serializer and invalidate it when the settings change. + private JsonSerializer _serializer; + + /// + /// Initializes a new instance. + /// + /// + /// The . Should be either the application-wide settings + /// () or an instance + /// initially returned. + /// + /// The . + public JsonOutputFormatter(JsonSerializerSettings serializerSettings, ArrayPool charPool) + { + if (serializerSettings == null) + { + throw new ArgumentNullException(nameof(serializerSettings)); + } + + if (charPool == null) + { + throw new ArgumentNullException(nameof(charPool)); + } + + SerializerSettings = serializerSettings; + _charPool = new JsonArrayPool(charPool); + + SupportedEncodings.Add(Encoding.UTF8); + SupportedEncodings.Add(Encoding.Unicode); + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJson); + SupportedMediaTypes.Add(MediaTypeHeaderValues.TextJson); + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyJsonSyntax); + } + + /// + /// Gets the used to configure the . + /// + /// + /// Any modifications to the object after this + /// has been used will have no effect. + /// + protected JsonSerializerSettings SerializerSettings { get; } + + /// + /// Gets the used to configure the . + /// + /// + /// Any modifications to the object after this + /// has been used will have no effect. + /// + [EditorBrowsable(EditorBrowsableState.Never)] + public JsonSerializerSettings PublicSerializerSettings => SerializerSettings; + + /// + /// Writes the given as JSON using the given + /// . + /// + /// The used to write the + /// The value to write as JSON. + public void WriteObject(TextWriter writer, object value) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + using (var jsonWriter = CreateJsonWriter(writer)) + { + var jsonSerializer = CreateJsonSerializer(); + jsonSerializer.Serialize(jsonWriter, value); + } + } + + /// + /// Called during serialization to create the . + /// + /// The used to write. + /// The used during serialization. + protected virtual JsonWriter CreateJsonWriter(TextWriter writer) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + var jsonWriter = new JsonTextWriter(writer) + { + ArrayPool = _charPool, + CloseOutput = false, + AutoCompleteOnClose = false + }; + + return jsonWriter; + } + + /// + /// Called during serialization to create the . + /// + /// The used during serialization and deserialization. + protected virtual JsonSerializer CreateJsonSerializer() + { + if (_serializer == null) + { + _serializer = JsonSerializer.Create(SerializerSettings); + } + + return _serializer; + } + + /// + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (selectedEncoding == null) + { + throw new ArgumentNullException(nameof(selectedEncoding)); + } + + var response = context.HttpContext.Response; + using (var writer = context.WriterFactory(response.Body, selectedEncoding)) + { + WriteObject(writer, context.Object); + + // 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/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchExtensions.cs new file mode 100644 index 0000000000..e94b221f69 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchExtensions.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET 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.JsonPatch; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Extensions for + /// + public static class JsonPatchExtensions + { + /// + /// Applies JSON patch operations on object and logs errors in . + /// + /// The . + /// The entity on which is applied. + /// The to add errors. + public static void ApplyTo( + this JsonPatchDocument patchDoc, + T objectToApplyTo, + ModelStateDictionary modelState) where T : class + { + if (patchDoc == null) + { + throw new ArgumentNullException(nameof(patchDoc)); + } + + if (objectToApplyTo == null) + { + throw new ArgumentNullException(nameof(objectToApplyTo)); + } + + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + patchDoc.ApplyTo(objectToApplyTo, modelState, prefix: string.Empty); + } + + /// + /// Applies JSON patch operations on object and logs errors in . + /// + /// The . + /// The entity on which is applied. + /// The to add errors. + /// The prefix to use when looking up values in . + public static void ApplyTo( + this JsonPatchDocument patchDoc, + T objectToApplyTo, + ModelStateDictionary modelState, + string prefix) where T : class + { + if (patchDoc == null) + { + throw new ArgumentNullException(nameof(patchDoc)); + } + + if (objectToApplyTo == null) + { + throw new ArgumentNullException(nameof(objectToApplyTo)); + } + + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + patchDoc.ApplyTo(objectToApplyTo, jsonPatchError => + { + var affectedObjectName = jsonPatchError.AffectedObject.GetType().Name; + var key = string.IsNullOrEmpty(prefix) ? affectedObjectName : prefix + "." + affectedObjectName; + + modelState.TryAddModelError(key, jsonPatchError.ErrorMessage); + }); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs new file mode 100644 index 0000000000..96549d44d5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs @@ -0,0 +1,184 @@ +// Copyright (c) .NET 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.Buffers; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.JsonPatch; +using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.ObjectPool; +using Newtonsoft.Json; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// A for JSON Patch (application/json-patch+json) content. + /// + public class JsonPatchInputFormatter : JsonInputFormatter + { + /// + /// Initializes a new instance. + /// + /// The . + /// + /// The . Should be either the application-wide settings + /// () or an instance + /// initially returned. + /// + /// The . + /// The . + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public JsonPatchInputFormatter( + ILogger logger, + JsonSerializerSettings serializerSettings, + ArrayPool charPool, + ObjectPoolProvider objectPoolProvider) + : this(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering: false) + { + } + + /// + /// Initializes a new instance. + /// + /// The . + /// + /// The . Should be either the application-wide settings + /// () or an instance + /// initially returned. + /// + /// The . + /// The . + /// Flag to buffer entire request body before deserializing it. + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public JsonPatchInputFormatter( + ILogger logger, + JsonSerializerSettings serializerSettings, + ArrayPool charPool, + ObjectPoolProvider objectPoolProvider, + bool suppressInputFormatterBuffering) + : this(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, allowInputFormatterExceptionMessages: false) + { + } + + /// + /// Initializes a new instance. + /// + /// The . + /// + /// The . Should be either the application-wide settings + /// () or an instance + /// initially returned. + /// + /// The . + /// The . + /// Flag to buffer entire request body before deserializing it. + /// + /// If , JSON deserialization exception messages will replaced by a generic message in model state. + /// + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public JsonPatchInputFormatter( + ILogger logger, + JsonSerializerSettings serializerSettings, + ArrayPool charPool, + ObjectPoolProvider objectPoolProvider, + bool suppressInputFormatterBuffering, + bool allowInputFormatterExceptionMessages) + : base(logger, serializerSettings, charPool, objectPoolProvider, suppressInputFormatterBuffering, allowInputFormatterExceptionMessages) + { + // Clear all values and only include json-patch+json value. + SupportedMediaTypes.Clear(); + + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJsonPatch); + } + + /// + /// Initializes a new instance. + /// + /// The . + /// + /// The . Should be either the application-wide settings + /// () or an instance + /// initially returned. + /// + /// The . + /// The . + /// The . + /// The . + public JsonPatchInputFormatter( + ILogger logger, + JsonSerializerSettings serializerSettings, + ArrayPool charPool, + ObjectPoolProvider objectPoolProvider, + MvcOptions options, + MvcJsonOptions jsonOptions) + : base(logger, serializerSettings, charPool, objectPoolProvider, options, jsonOptions) + { + // Clear all values and only include json-patch+json value. + SupportedMediaTypes.Clear(); + + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationJsonPatch); + } + + /// + public override InputFormatterExceptionPolicy ExceptionPolicy + { + get + { + if (GetType() == typeof(JsonPatchInputFormatter)) + { + return InputFormatterExceptionPolicy.MalformedInputExceptions; + } + return InputFormatterExceptionPolicy.AllExceptions; + } + } + + /// + public async override Task ReadRequestBodyAsync( + InputFormatterContext context, + Encoding encoding) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + var result = await base.ReadRequestBodyAsync(context, encoding); + if (!result.HasError) + { + var jsonPatchDocument = (IJsonPatchDocument)result.Model; + if (jsonPatchDocument != null && SerializerSettings.ContractResolver != null) + { + jsonPatchDocument.ContractResolver = SerializerSettings.ContractResolver; + } + } + + return result; + } + + /// + public override bool CanRead(InputFormatterContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var modelTypeInfo = context.ModelType.GetTypeInfo(); + if (!typeof(IJsonPatchDocument).GetTypeInfo().IsAssignableFrom(modelTypeInfo) || + !modelTypeInfo.IsGenericType) + { + return false; + } + + return base.CanRead(context); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchOperationsArrayProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchOperationsArrayProvider.cs new file mode 100644 index 0000000000..840df5fdf8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchOperationsArrayProvider.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 System.Reflection; +using Microsoft.AspNetCore.JsonPatch; +using Microsoft.AspNetCore.JsonPatch.Operations; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Json +{ + /// + /// Implements a provider of to change parameters of + /// type to an array of . + /// + public class JsonPatchOperationsArrayProvider : IApiDescriptionProvider + { + private readonly IModelMetadataProvider _modelMetadataProvider; + + /// + /// Creates a new instance of . + /// + /// The . + public JsonPatchOperationsArrayProvider(IModelMetadataProvider modelMetadataProvider) + { + _modelMetadataProvider = modelMetadataProvider; + } + + /// + /// + /// The order -999 ensures that this provider is executed right after the Microsoft.AspNetCore.Mvc.ApiExplorer.DefaultApiDescriptionProvider. + /// + public int Order => -999; + + /// + public void OnProvidersExecuting(ApiDescriptionProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + foreach (var result in context.Results) + { + foreach (var parameterDescription in result.ParameterDescriptions) + { + if (typeof(IJsonPatchDocument).GetTypeInfo().IsAssignableFrom(parameterDescription.Type)) + { + parameterDescription.Type = typeof(Operation[]); + parameterDescription.ModelMetadata = _modelMetadataProvider.GetMetadataForType(typeof(Operation[])); + } + } + } + } + + /// + public void OnProvidersExecuted(ApiDescriptionProviderContext context) + { + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonResult.cs new file mode 100644 index 0000000000..4380224057 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonResult.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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An action result which formats the given object as JSON. + /// + public class JsonResult : ActionResult + { + /// + /// Creates a new with the given . + /// + /// The value to format as JSON. + public JsonResult(object value) + { + Value = value; + } + + /// + /// Creates a new with the given . + /// + /// The value to format as JSON. + /// The to be used by + /// the formatter. + public JsonResult(object value, JsonSerializerSettings serializerSettings) + { + if (serializerSettings == null) + { + throw new ArgumentNullException(nameof(serializerSettings)); + } + + Value = value; + SerializerSettings = serializerSettings; + } + + /// + /// Gets or sets the representing the Content-Type header of the response. + /// + public string ContentType { get; set; } + + /// + /// Gets or sets the . + /// + public JsonSerializerSettings SerializerSettings { get; set; } + + /// + /// Gets or sets the HTTP status code. + /// + public int? StatusCode { get; set; } + + /// + /// Gets or sets the value to be formatted. + /// + public object Value { get; set; } + + /// + public override Task ExecuteResultAsync(ActionContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var services = context.HttpContext.RequestServices; + var executor = services.GetRequiredService(); + return executor.ExecuteAsync(context, this); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs new file mode 100644 index 0000000000..8b6761559d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// Helper class which provides . + /// + public static class JsonSerializerSettingsProvider + { + private const int DefaultMaxDepth = 32; + + // 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 + { + NamingStrategy = new CamelCaseNamingStrategy(), + }; + + /// + /// Creates default . + /// + /// Default . + public static JsonSerializerSettings CreateSerializerSettings() + { + return new JsonSerializerSettings + { + ContractResolver = SharedContractResolver, + + MissingMemberHandling = MissingMemberHandling.Ignore, + + // Limit the object graph we'll consume to a fixed depth. This prevents stackoverflow exceptions + // from deserialization errors that might occur from deeply nested objects. + MaxDepth = DefaultMaxDepth, + + // Do not change this setting + // Setting this to None prevents Json.NET from loading malicious, unsafe, or security-sensitive types + TypeNameHandling = TypeNameHandling.None, + }; + } + } +} diff --git a/src/Mvc/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 new file mode 100644 index 0000000000..608e370257 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj @@ -0,0 +1,17 @@ + + + + ASP.NET Core MVC formatters for JSON input and output and for JSON PATCH input using Json.NET. + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;aspnetcoremvc;json + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs new file mode 100644 index 0000000000..f27cf0b00c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET 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.Formatters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Newtonsoft.Json; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Provides programmatic configuration for JSON in the MVC framework. + /// + public class MvcJsonOptions : IEnumerable + { + private readonly CompatibilitySwitch _allowInputFormatterExceptionMessages; + private readonly ICompatibilitySwitch[] _switches; + + /// + /// Creates a new instance of . + /// + public MvcJsonOptions() + { + _allowInputFormatterExceptionMessages = new CompatibilitySwitch(nameof(AllowInputFormatterExceptionMessages)); + + _switches = new ICompatibilitySwitch[] + { + _allowInputFormatterExceptionMessages, + }; + } + + /// + /// Gets or sets a flag to determine whether error messsages 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. + /// + /// + /// + /// Error messages in the are often communicated to clients, either in HTML + /// or using . In effect, this setting controls whether clients can receive + /// detailed error messages about submitted JSON data. + /// + /// + /// 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 false unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have value true unless explicitly configured. + /// + /// + public bool AllowInputFormatterExceptionMessages + { + get => _allowInputFormatterExceptionMessages.Value; + set => _allowInputFormatterExceptionMessages.Value = value; + } + + /// + /// Gets the that are used by this application. + /// + public JsonSerializerSettings SerializerSettings { get; } = JsonSerializerSettingsProvider.CreateSerializerSettings(); + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_switches).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator(); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsConfigureCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsConfigureCompatibilityOptions.cs new file mode 100644 index 0000000000..f33e6dc108 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsConfigureCompatibilityOptions.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 +{ + internal class MvcJsonOptionsConfigureCompatibilityOptions : ConfigureCompatibilityOptions + { + public MvcJsonOptionsConfigureCompatibilityOptions( + ILoggerFactory loggerFactory, + IOptions compatibilityOptions) + : base(loggerFactory, compatibilityOptions) + { + } + + protected override IReadOnlyDictionary DefaultValues + { + get + { + var values = new Dictionary(); + + if (Version >= CompatibilityVersion.Version_2_1) + { + values[nameof(MvcJsonOptions.AllowInputFormatterExceptionMessages)] = true; + } + + return values; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/baseline.netcore.json new file mode 100644 index 0000000000..9a1c0bda85 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/baseline.netcore.json @@ -0,0 +1,910 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Formatters.Json, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.Mvc.JsonPatchExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "ApplyTo", + "Parameters": [ + { + "Name": "patchDoc", + "Type": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument" + }, + { + "Name": "objectToApplyTo", + "Type": "T0" + }, + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "ApplyTo", + "Parameters": [ + { + "Name": "patchDoc", + "Type": "Microsoft.AspNetCore.JsonPatch.JsonPatchDocument" + }, + { + "Name": "objectToApplyTo", + "Type": "T0" + }, + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + }, + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.JsonResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ContentType", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SerializerSettings", + "Parameters": [], + "ReturnType": "Newtonsoft.Json.JsonSerializerSettings", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SerializerSettings", + "Parameters": [ + { + "Name": "value", + "Type": "Newtonsoft.Json.JsonSerializerSettings" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_StatusCode", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_StatusCode", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Value", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Value", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + }, + { + "Name": "serializerSettings", + "Type": "Newtonsoft.Json.JsonSerializerSettings" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.MvcJsonOptions", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "Newtonsoft.Json.JsonSerializerSettings", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.JsonInputFormatter", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter", + "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", + "Parameters": [], + "ReturnType": "Newtonsoft.Json.JsonSerializerSettings", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReadRequestBodyAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateJsonSerializer", + "Parameters": [], + "ReturnType": "Newtonsoft.Json.JsonSerializer", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReleaseJsonSerializer", + "Parameters": [ + { + "Name": "serializer", + "Type": "Newtonsoft.Json.JsonSerializer" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "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" + } + ], + "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" + } + ], + "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.JsonOutputFormatter", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_SerializerSettings", + "Parameters": [], + "ReturnType": "Newtonsoft.Json.JsonSerializerSettings", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PublicSerializerSettings", + "Parameters": [], + "ReturnType": "Newtonsoft.Json.JsonSerializerSettings", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteObject", + "Parameters": [ + { + "Name": "writer", + "Type": "System.IO.TextWriter" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateJsonWriter", + "Parameters": [ + { + "Name": "writer", + "Type": "System.IO.TextWriter" + } + ], + "ReturnType": "Newtonsoft.Json.JsonWriter", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateJsonSerializer", + "Parameters": [], + "ReturnType": "Newtonsoft.Json.JsonSerializer", + "Virtual": true, + "Visibility": "Protected", + "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, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "serializerSettings", + "Type": "Newtonsoft.Json.JsonSerializerSettings" + }, + { + "Name": "charPool", + "Type": "System.Buffers.ArrayPool" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.JsonPatchInputFormatter", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "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" + } + ], + "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" + } + ], + "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.JsonSerializerSettingsProvider", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateSerializerSettings", + "Parameters": [], + "ReturnType": "Newtonsoft.Json.JsonSerializerSettings", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Json.JsonPatchOperationsArrayProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Int32", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionProviderContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionProviderContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "modelMetadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcJsonMvcBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddJsonOptions", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcJsonMvcCoreBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddJsonFormatters", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddJsonFormatters", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddJsonOptions", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerable.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerable.cs new file mode 100644 index 0000000000..e548794e9e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerable.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; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Serializes types by delegating them through a concrete implementation. + /// + /// The wrapping or original type of the + /// to proxy. + /// The type parameter of the original + /// to proxy. + public class DelegatingEnumerable : IEnumerable + { + private readonly IEnumerable _source; + private readonly IWrapperProvider _wrapperProvider; + + /// + /// Initializes a . + /// + /// + /// This constructor is necessary for + /// to serialize. + /// + public DelegatingEnumerable() + { + _source = Enumerable.Empty(); + } + + /// + /// Initializes a with the original + /// and the wrapper provider for wrapping individual elements. + /// + /// The instance to get the enumerator from. + /// The wrapper provider for wrapping individual elements. + public DelegatingEnumerable(IEnumerable source, IWrapperProvider elementWrapperProvider) + { + if (source == null) + { + throw new ArgumentNullException(nameof(source)); + } + + _source = source; + _wrapperProvider = elementWrapperProvider; + } + + /// + /// Gets a delegating enumerator of the original source which is being + /// wrapped. + /// + /// The delegating enumerator of the original source. + public IEnumerator GetEnumerator() + { + return new DelegatingEnumerator(_source.GetEnumerator(), _wrapperProvider); + } + + /// + /// The serializer requires every type it encounters can be serialized and deserialized. + /// This type will never be used for deserialization, but we are required to implement the add + /// method so that the type can be serialized. This will never be called. + /// + /// The item to add. Unused. + public void Add(object item) + { + throw new NotImplementedException(); + } + + /// + /// Gets a delegating enumerator of the original source which is being + /// wrapped. + /// + /// The delegating enumerator of the original source. + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerator.cs new file mode 100644 index 0000000000..f59b5cdfa7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerator.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.Collections; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Delegates enumeration of elements to the original enumerator and wraps the items + /// with the supplied . + /// + /// The type to which the individual elements need to be wrapped to. + /// The original type of the element being wrapped. + public class DelegatingEnumerator : IEnumerator + { + private readonly IEnumerator _inner; + private readonly IWrapperProvider _wrapperProvider; + + /// + /// Initializes a which enumerates + /// over the elements of the original enumerator and wraps them using the supplied + /// . + /// + /// The original enumerator. + /// The wrapper provider to wrap individual elements. + public DelegatingEnumerator(IEnumerator inner, IWrapperProvider wrapperProvider) + { + if (inner == null) + { + throw new ArgumentNullException(nameof(inner)); + } + + _inner = inner; + _wrapperProvider = wrapperProvider; + } + + /// + public TWrapped Current + { + get + { + object obj = _inner.Current; + if (_wrapperProvider == null) + { + // if there is no wrapper, then this cast should not fail + return (TWrapped)obj; + } + + return (TWrapped)_wrapperProvider.Wrap(obj); + } + } + + /// + object IEnumerator.Current => Current; + + /// + public void Dispose() + { + _inner.Dispose(); + } + + /// + public bool MoveNext() + { + return _inner.MoveNext(); + } + + /// + public void Reset() + { + _inner.Reset(); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcBuilderExtensions.cs new file mode 100644 index 0000000000..4210fbe0bc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcBuilderExtensions.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; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for adding XML formatters to MVC. + /// + public static class MvcXmlMvcBuilderExtensions + { + /// + /// Adds the XML DataContractSerializer formatters to MVC. + /// + /// The . + /// The . + public static IMvcBuilder AddXmlDataContractSerializerFormatters(this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddXmlDataContractSerializerFormatterServices(builder.Services); + return builder; + } + + /// + /// Adds the XML Serializer formatters to MVC. + /// + /// The . + /// The . + public static IMvcBuilder AddXmlSerializerFormatters(this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddXmlSerializerFormatterServices(builder.Services); + return builder; + } + + // Internal for testing. + internal static void AddXmlDataContractSerializerFormatterServices(IServiceCollection services) + { + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcXmlDataContractSerializerMvcOptionsSetup>()); + } + + // Internal for testing. + internal static void AddXmlSerializerFormatterServices(IServiceCollection services) + { + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcXmlSerializerMvcOptionsSetup>()); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcCoreBuilderExtensions.cs new file mode 100644 index 0000000000..5e14f41647 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcCoreBuilderExtensions.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; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for adding XML formatters to MVC. + /// + public static class MvcXmlMvcCoreBuilderExtensions + { + /// + /// Adds the XML DataContractSerializer formatters to MVC. + /// + /// The . + /// The . + public static IMvcCoreBuilder AddXmlDataContractSerializerFormatters(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddXmlDataContractSerializerFormatterServices(builder.Services); + return builder; + } + + /// + /// Adds the XML Serializer formatters to MVC. + /// + /// The . + /// The . + public static IMvcCoreBuilder AddXmlSerializerFormatters(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddXmlSerializerFormatterServices(builder.Services); + return builder; + } + + // Internal for testing. + internal static void AddXmlDataContractSerializerFormatterServices(IServiceCollection services) + { + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcXmlDataContractSerializerMvcOptionsSetup>()); + } + + // Internal for testing. + internal static void AddXmlSerializerFormatterServices(IServiceCollection services) + { + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcXmlSerializerMvcOptionsSetup>()); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProvider.cs new file mode 100644 index 0000000000..ada373cf9d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProvider.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.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Provides a for interface types which implement + /// . + /// + public class EnumerableWrapperProvider : IWrapperProvider + { + private readonly IWrapperProvider _wrapperProvider; + private readonly ConstructorInfo _wrappingTypeConstructor; + + /// + /// Initializes an instance of . + /// + /// Type of the original + /// that is being wrapped. + /// The for the element type. + /// Can be null. + public EnumerableWrapperProvider( + Type sourceEnumerableOfT, + IWrapperProvider elementWrapperProvider) + { + if (sourceEnumerableOfT == null) + { + throw new ArgumentNullException(nameof(sourceEnumerableOfT)); + } + + var enumerableOfT = ClosedGenericMatcher.ExtractGenericInterface( + sourceEnumerableOfT, + typeof(IEnumerable<>)); + if (!sourceEnumerableOfT.GetTypeInfo().IsInterface || enumerableOfT == null) + { + throw new ArgumentException( + Resources.FormatEnumerableWrapperProvider_InvalidSourceEnumerableOfT(typeof(IEnumerable<>).Name), + nameof(sourceEnumerableOfT)); + } + + _wrapperProvider = elementWrapperProvider; + + var declaredElementType = enumerableOfT.GenericTypeArguments[0]; + var wrappedElementType = elementWrapperProvider?.WrappingType ?? declaredElementType; + WrappingType = typeof(DelegatingEnumerable<,>).MakeGenericType(wrappedElementType, declaredElementType); + + _wrappingTypeConstructor = WrappingType.GetConstructor(new[] + { + sourceEnumerableOfT, + typeof(IWrapperProvider) + }); + } + + /// + public Type WrappingType + { + get; + } + + /// + public object Wrap(object original) + { + if (original == null) + { + return null; + } + + return _wrappingTypeConstructor.Invoke(new[] { original, _wrapperProvider }); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProviderFactory.cs new file mode 100644 index 0000000000..9438e87986 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProviderFactory.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.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Creates an for interface types implementing the + /// type. + /// + public class EnumerableWrapperProviderFactory : IWrapperProviderFactory + { + private readonly IEnumerable _wrapperProviderFactories; + + /// + /// Initializes an with a list + /// . + /// + /// List of . + public EnumerableWrapperProviderFactory(IEnumerable wrapperProviderFactories) + { + if (wrapperProviderFactories == null) + { + throw new ArgumentNullException(nameof(wrapperProviderFactories)); + } + + _wrapperProviderFactories = wrapperProviderFactories; + } + + /// + /// Gets an for the provided context. + /// + /// The . + /// An instance of if the declared type is + /// an interface and implements . + public IWrapperProvider GetProvider(WrapperProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.IsSerialization) + { + // Example: IEnumerable + var declaredType = context.DeclaredType; + var declaredTypeInfo = declaredType.GetTypeInfo(); + + // We only wrap interfaces types(ex: IEnumerable, IQueryable, IList etc.) and not + // concrete types like List, Collection which implement IEnumerable. + if (declaredType != null && declaredTypeInfo.IsInterface && declaredTypeInfo.IsGenericType) + { + var enumerableOfT = ClosedGenericMatcher.ExtractGenericInterface( + declaredType, + typeof(IEnumerable<>)); + if (enumerableOfT != null) + { + var elementType = enumerableOfT.GenericTypeArguments[0]; + var wrapperProviderContext = new WrapperProviderContext(elementType, context.IsSerialization); + + var elementWrapperProvider = + _wrapperProviderFactories.GetWrapperProvider(wrapperProviderContext); + + return new EnumerableWrapperProvider(enumerableOfT, elementWrapperProvider); + } + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IUnwrappable.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IUnwrappable.cs new file mode 100644 index 0000000000..31f9cf4917 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IUnwrappable.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 Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Defines an interface for objects to be un-wrappable after deserialization. + /// + public interface IUnwrappable + { + /// + /// Unwraps an object. + /// + /// The type to which the object should be un-wrapped to. + /// The un-wrapped object. + object Unwrap(Type declaredType); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProvider.cs new file mode 100644 index 0000000000..3c2c33c376 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProvider.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; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Defines an interface for wrapping objects for serialization or deserialization into xml. + /// + public interface IWrapperProvider + { + /// + /// Gets the wrapping type. + /// + Type WrappingType { get; } + + /// + /// Wraps the given object to the wrapping type provided by . + /// + /// The original non-wrapped object. + /// Returns a wrapped object. + object Wrap(object original); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProviderFactory.cs new file mode 100644 index 0000000000..9594d09d75 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProviderFactory.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.Formatters.Xml +{ + /// + /// Create a given a . + /// + public interface IWrapperProviderFactory + { + /// + /// Gets the for the provided context. + /// + /// The . + /// A wrapping provider if the factory decides to wrap the type, else null. + IWrapperProvider GetProvider(WrapperProviderContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/FormattingUtilities.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/FormattingUtilities.cs new file mode 100644 index 0000000000..7b97d7ca79 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/FormattingUtilities.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.Runtime.Serialization; +using System.Xml; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal +{ + /// + /// Contains methods which are used by Xml input formatters. + /// + public static class FormattingUtilities + { + public static readonly int DefaultMaxDepth = 32; + public static readonly XsdDataContractExporter XsdDataContractExporter = new XsdDataContractExporter(); + + /// + /// Gets the default Reader Quotas for XmlReader. + /// + /// XmlReaderQuotas with default values + public static XmlDictionaryReaderQuotas GetDefaultXmlReaderQuotas() + { + return new XmlDictionaryReaderQuotas() + { + MaxArrayLength = int.MaxValue, + MaxBytesPerRead = int.MaxValue, + MaxDepth = DefaultMaxDepth, + MaxNameTableCharCount = int.MaxValue, + MaxStringContentLength = int.MaxValue + }; + } + + /// + /// Gets the default XmlWriterSettings. + /// + /// Default + public static XmlWriterSettings GetDefaultXmlWriterSettings() + { + return new XmlWriterSettings + { + OmitXmlDeclaration = true, + CloseOutput = false, + CheckCharacters = false + }; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MediaTypeHeaderValues.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MediaTypeHeaderValues.cs new file mode 100644 index 0000000000..14f2a99ce4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MediaTypeHeaderValues.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 Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal +{ + internal static class MediaTypeHeaderValues + { + public static readonly MediaTypeHeaderValue ApplicationXml + = MediaTypeHeaderValue.Parse("application/xml").CopyAsReadOnly(); + + public static readonly MediaTypeHeaderValue TextXml + = MediaTypeHeaderValue.Parse("text/xml").CopyAsReadOnly(); + + public static readonly MediaTypeHeaderValue ApplicationAnyXmlSyntax + = MediaTypeHeaderValue.Parse("application/*+xml").CopyAsReadOnly(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs new file mode 100644 index 0000000000..c1cd77f685 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET 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.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 +{ + /// + /// A implementation which will add the + /// data contract serializer formatters to . + /// + public class MvcXmlDataContractSerializerMvcOptionsSetup : IConfigureOptions + { + private readonly ILoggerFactory _loggerFactory; + + /// + /// Initializes a new instance of . + /// + /// The . + public MvcXmlDataContractSerializerMvcOptionsSetup(ILoggerFactory loggerFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _loggerFactory = loggerFactory; + } + + /// + /// Adds the data contract serializer formatters to . + /// + /// The . + public void Configure(MvcOptions options) + { + options.ModelMetadataDetailsProviders.Add(new DataMemberRequiredBindingMetadataProvider()); + + options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter(_loggerFactory)); + options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options)); + + // Do not override any user mapping + var key = "xml"; + var mapping = options.FormatterMappings.GetMediaTypeMappingForFormat(key); + if (string.IsNullOrEmpty(mapping)) + { + options.FormatterMappings.SetMediaTypeMappingForFormat( + key, + MediaTypeHeaderValues.ApplicationXml); + } + + options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider("System.Xml.Linq.XObject")); + options.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider("System.Xml.XmlNode")); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs new file mode 100644 index 0000000000..6c7546e332 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.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 Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal +{ + /// + /// A implementation which will add the + /// XML serializer formatters to . + /// + public class MvcXmlSerializerMvcOptionsSetup : IConfigureOptions + { + private readonly ILoggerFactory _loggerFactory; + + /// + /// Initializes a new instance of . + /// + /// The . + public MvcXmlSerializerMvcOptionsSetup(ILoggerFactory loggerFactory) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _loggerFactory = loggerFactory; + } + + /// + /// Adds the XML serializer formatters to . + /// + /// The . + public void Configure(MvcOptions options) + { + // Do not override any user mapping + var key = "xml"; + var mapping = options.FormatterMappings.GetMediaTypeMappingForFormat(key); + if (string.IsNullOrEmpty(mapping)) + { + options.FormatterMappings.SetMediaTypeMappingForFormat( + key, + MediaTypeHeaderValues.ApplicationXml); + } + + options.OutputFormatters.Add(new XmlSerializerOutputFormatter(_loggerFactory)); + options.InputFormatters.Add(new XmlSerializerInputFormatter(options)); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/LoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/LoggerExtensions.cs new file mode 100644 index 0000000000..dfb00627d4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/LoggerExtensions.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; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal +{ + public static class LoggerExtensions + { + private static readonly Action _failedToCreateXmlSerializer; + private static readonly Action _failedToCreateDataContractSerializer; + + static LoggerExtensions() + { + _failedToCreateXmlSerializer = LoggerMessage.Define( + LogLevel.Warning, + 1, + "An error occurred while trying to create an XmlSerializer for the type '{Type}'."); + + _failedToCreateDataContractSerializer = LoggerMessage.Define( + LogLevel.Warning, + 2, + "An error occurred while trying to create a DataContractSerializer for the type '{Type}'."); + } + + public static void FailedToCreateXmlSerializer(this ILogger logger, string typeName, Exception exception) + { + _failedToCreateXmlSerializer(logger, typeName, exception); + } + + public static void FailedToCreateDataContractSerializer(this ILogger logger, string typeName, Exception exception) + { + _failedToCreateDataContractSerializer(logger, typeName, exception); + } + } +} diff --git a/src/Mvc/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 new file mode 100644 index 0000000000..b716f5cbbb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj @@ -0,0 +1,17 @@ + + + + ASP.NET Core MVC formatters for XML input and output using DataContractSerializer and XmlSerializer. + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;aspnetcoremvc;xml + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ModelBinding/DataMemberRequiredBindingMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ModelBinding/DataMemberRequiredBindingMetadataProvider.cs new file mode 100644 index 0000000000..d1c6002158 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ModelBinding/DataMemberRequiredBindingMetadataProvider.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 System.Runtime.Serialization; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + /// + /// An for . + /// + public class DataMemberRequiredBindingMetadataProvider : IBindingMetadataProvider + { + /// + public void CreateBindingMetadata(BindingMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // Types cannot be required; only properties can + if (context.Key.MetadataKind != ModelMetadataKind.Property) + { + return; + } + + if (context.BindingMetadata.IsBindingRequired) + { + // This value is already required, no need to look at attributes. + return; + } + + var dataMemberAttribute = context + .PropertyAttributes + .OfType() + .FirstOrDefault(); + if (dataMemberAttribute == null || !dataMemberAttribute.IsRequired) + { + return; + } + + // isDataContract == true iff the container type has at least one DataContractAttribute + var containerType = context.Key.ContainerType.GetTypeInfo(); + var isDataContract = containerType.IsDefined(typeof(DataContractAttribute)); + if (isDataContract) + { + // We don't need to add a validator, just to set IsRequired = true. The validation + // system will do the right thing. + context.BindingMetadata.IsBindingRequired = true; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..94864035dd --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs @@ -0,0 +1,86 @@ +// +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Mvc.Formatters.Xml.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// The type must be an interface and must be or derive from '{0}'. + /// + internal static string EnumerableWrapperProvider_InvalidSourceEnumerableOfT + { + get => GetString("EnumerableWrapperProvider_InvalidSourceEnumerableOfT"); + } + + /// + /// The type must be an interface and must be or derive from '{0}'. + /// + internal static string FormatEnumerableWrapperProvider_InvalidSourceEnumerableOfT(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("EnumerableWrapperProvider_InvalidSourceEnumerableOfT"), p0); + + /// + /// An error occurred while deserializing input data. + /// + internal static string ErrorDeserializingInputData + { + get => GetString("ErrorDeserializingInputData"); + } + + /// + /// An error occurred while deserializing input data. + /// + internal static string FormatErrorDeserializingInputData() + => GetString("ErrorDeserializingInputData"); + + /// + /// {0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'. + /// + internal static string RequiredProperty_MustHaveDataMemberRequired + { + get => GetString("RequiredProperty_MustHaveDataMemberRequired"); + } + + /// + /// {0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'. + /// + internal static string FormatRequiredProperty_MustHaveDataMemberRequired(object p0, object p1, object p2, object p3, object p4, object p5, object p6) + => string.Format(CultureInfo.CurrentCulture, GetString("RequiredProperty_MustHaveDataMemberRequired"), p0, p1, p2, p3, p4, p5, p6); + + /// + /// The object to be wrapped must be of type '{0}' but was of type '{1}'. + /// + internal static string WrapperProvider_MismatchType + { + get => GetString("WrapperProvider_MismatchType"); + } + + /// + /// The object to be wrapped must be of type '{0}' but was of type '{1}'. + /// + internal static string FormatWrapperProvider_MismatchType(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("WrapperProvider_MismatchType"), 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/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx new file mode 100644 index 0000000000..b83867ae64 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 type must be an interface and must be or derive from '{0}'. + + + An error occurred while deserializing input data. + + + {0} does not recognize '{1}', so instead use '{2}' with '{3}' set to '{4}' for value type property '{5}' on type '{6}'. + + + The object to be wrapped must be of type '{0}' but was of type '{1}'. + + \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs new file mode 100644 index 0000000000..d247b2fab9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.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.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("Error")] + public sealed class SerializableErrorWrapper : IXmlSerializable, IUnwrappable + { + // Note: XmlSerializer requires to have default constructor + public SerializableErrorWrapper() + { + SerializableError = new SerializableError(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The object that needs to be wrapped. + public SerializableErrorWrapper(SerializableError error) + { + if (error == null) + { + throw new ArgumentNullException(nameof(error)); + } + + SerializableError = error; + } + + /// + /// Gets the wrapped object which is serialized/deserialized into XML + /// representation. + /// + public SerializableError SerializableError { get; } + + /// + public XmlSchema GetSchema() + { + return null; + } + + /// + /// Generates a object from its XML representation. + /// + /// The stream from which the object is deserialized. + public void ReadXml(XmlReader reader) + { + if (reader.IsEmptyElement) + { + reader.Read(); + return; + } + + reader.ReadStartElement(); + while (reader.NodeType != XmlNodeType.EndElement) + { + var key = XmlConvert.DecodeName(reader.LocalName); + var value = reader.ReadInnerXml(); + + SerializableError.Add(key, value); + reader.MoveToContent(); + } + + reader.ReadEndElement(); + } + + /// + /// Converts the wrapped object into its XML representation. + /// + /// The stream to which the object is serialized. + public void WriteXml(XmlWriter writer) + { + foreach (var keyValuePair in SerializableError) + { + var key = keyValuePair.Key; + var value = keyValuePair.Value; + writer.WriteStartElement(XmlConvert.EncodeLocalName(key)); + if (value != null) + { + writer.WriteValue(value); + } + + writer.WriteEndElement(); + } + } + + /// + public object Unwrap(Type declaredType) + { + if (declaredType == null) + { + throw new ArgumentNullException(nameof(declaredType)); + } + + return SerializableError; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProvider.cs new file mode 100644 index 0000000000..bd83514e6a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProvider.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; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Wraps the object of type . + /// + public class SerializableErrorWrapperProvider : IWrapperProvider + { + /// + public Type WrappingType => typeof(SerializableErrorWrapper); + + /// + public object Wrap(object original) + { + if (original == null) + { + throw new ArgumentNullException(nameof(original)); + } + + var error = original as SerializableError; + if (error == null) + { + throw new ArgumentException( + Resources.FormatWrapperProvider_MismatchType( + typeof(SerializableErrorWrapper).Name, + original.GetType().Name), + nameof(original)); + } + + return new SerializableErrorWrapper(error); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProviderFactory.cs new file mode 100644 index 0000000000..5dfdb652f0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProviderFactory.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; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Creates an for the type . + /// + public class SerializableErrorWrapperProviderFactory : IWrapperProviderFactory + { + /// + /// Creates an instance of if the provided + /// 's is + /// . + /// + /// The . + /// + /// An instance of if the provided 's + /// is + /// ; otherwise null. + /// + public IWrapperProvider GetProvider(WrapperProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.DeclaredType == typeof(SerializableError)) + { + return new SerializableErrorWrapperProvider(); + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderContext.cs new file mode 100644 index 0000000000..3164f67b69 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderContext.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; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// The context used by an to wrap or un-wrap types. + /// + public class WrapperProviderContext + { + /// + /// Initializes a . + /// + /// The declared type of the object that needs to be wrapped. + /// if the wrapper provider is invoked during + /// serialization, otherwise . + public WrapperProviderContext(Type declaredType, bool isSerialization) + { + if (declaredType == null) + { + throw new ArgumentNullException(nameof(declaredType)); + } + + DeclaredType = declaredType; + IsSerialization = isSerialization; + } + + /// + /// The declared type which could be wrapped/un-wrapped by a different type + /// during serialization or deserialization. + /// + public Type DeclaredType { get; } + + /// + /// if a wrapper provider is invoked during serialization, + /// otherwise. + /// + public bool IsSerialization { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs new file mode 100644 index 0000000000..1ccea1645d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Extension methods for . + /// + public static class WrapperProviderFactoriesExtensions + { + /// + /// Gets an instance of for the supplied + /// type. + /// + /// A list of . + /// The . + /// An instance of if there is a wrapping provider for the + /// supplied type, else null. + public static IWrapperProvider GetWrapperProvider( + this IEnumerable wrapperProviderFactories, + WrapperProviderContext wrapperProviderContext) + { + if (wrapperProviderFactories == null) + { + throw new ArgumentNullException(nameof(wrapperProviderFactories)); + } + + if (wrapperProviderContext == null) + { + throw new ArgumentNullException(nameof(wrapperProviderContext)); + } + + foreach (var wrapperProviderFactory in wrapperProviderFactories) + { + var wrapperProvider = wrapperProviderFactory.GetProvider(wrapperProviderContext); + if (wrapperProvider != null) + { + return wrapperProvider; + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs new file mode 100644 index 0000000000..7916715002 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.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 System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Formatters.Xml; +using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.WebUtilities; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// This class handles deserialization of input XML data + /// to strongly-typed objects using . + /// + public class XmlDataContractSerializerInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy + { + private readonly ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); + private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas(); + private readonly bool _suppressInputFormatterBuffering; + private readonly MvcOptions _options; + private DataContractSerializerSettings _serializerSettings; + + /// + /// Initializes a new instance of . + /// + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public XmlDataContractSerializerInputFormatter() + { + SupportedEncodings.Add(UTF8EncodingWithoutBOM); + SupportedEncodings.Add(UTF16EncodingLittleEndian); + + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml); + SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml); + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax); + + _serializerSettings = new DataContractSerializerSettings(); + + WrapperProviderFactories = new List(); + WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); + } + + /// + /// Initializes a new instance of . + /// + /// Flag to buffer entire request body before deserializing it. + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public XmlDataContractSerializerInputFormatter(bool suppressInputFormatterBuffering) + : this() + { + _suppressInputFormatterBuffering = suppressInputFormatterBuffering; + } + + /// + /// Initializes a new instance of . + /// + /// The . + public XmlDataContractSerializerInputFormatter(MvcOptions options) +#pragma warning disable CS0618 + : this() +#pragma warning restore CS0618 + { + _options = options; + } + + /// + /// Gets the list of to + /// provide the wrapping type for de-serialization. + /// + public IList WrapperProviderFactories { get; } + + /// + /// Indicates the acceptable input XML depth. + /// + public int MaxDepth + { + get { return _readerQuotas.MaxDepth; } + set { _readerQuotas.MaxDepth = value; } + } + + /// + /// The quotas include - DefaultMaxDepth, DefaultMaxStringContentLength, DefaultMaxArrayLength, + /// DefaultMaxBytesPerRead, DefaultMaxNameTableCharCount + /// + public XmlDictionaryReaderQuotas XmlDictionaryReaderQuotas => _readerQuotas; + + /// + /// Gets or sets the used to configure the + /// . + /// + public DataContractSerializerSettings SerializerSettings + { + get => _serializerSettings; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _serializerSettings = value; + } + } + + /// + public virtual InputFormatterExceptionPolicy ExceptionPolicy + { + get + { + if (GetType() == typeof(XmlDataContractSerializerInputFormatter)) + { + return InputFormatterExceptionPolicy.MalformedInputExceptions; + } + return InputFormatterExceptionPolicy.AllExceptions; + } + } + + /// + public override async Task ReadRequestBodyAsync(InputFormatterContext context, Encoding encoding) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + var request = context.HttpContext.Request; + + var suppressInputFormatterBuffering = _options?.SuppressInputFormatterBuffering ?? _suppressInputFormatterBuffering; + + if (!request.Body.CanSeek && !suppressInputFormatterBuffering) + { + // XmlDataContractSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously + // read everything into a buffer, and then seek back to the beginning. + request.EnableBuffering(); + Debug.Assert(request.Body.CanSeek); + + await request.Body.DrainAsync(CancellationToken.None); + request.Body.Seek(0L, SeekOrigin.Begin); + } + + try + { + using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), encoding)) + { + var type = GetSerializableType(context.ModelType); + var serializer = GetCachedSerializer(type); + + var deserializedObject = serializer.ReadObject(xmlReader); + + // Unwrap only if the original type was wrapped. + if (type != context.ModelType) + { + if (deserializedObject is IUnwrappable unwrappable) + { + deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType); + } + } + + return InputFormatterResult.Success(deserializedObject); + } + } + catch (SerializationException exception) + { + throw new InputFormatterException(Resources.ErrorDeserializingInputData, exception); + } + } + + /// + protected override bool CanReadType(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + return GetCachedSerializer(GetSerializableType(type)) != null; + } + + /// + /// Called during deserialization to get the . + /// + /// The from which to read. + /// The used to read the stream. + /// The used during deserialization. + protected virtual XmlReader CreateXmlReader(Stream readStream, Encoding encoding) + { + if (readStream == null) + { + throw new ArgumentNullException(nameof(readStream)); + } + + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + return XmlDictionaryReader.CreateTextReader(readStream, encoding, _readerQuotas, onClose: null); + } + + /// + /// Gets the type to which the XML will be deserialized. + /// + /// The declared type. + /// The type to which the XML will be deserialized. + protected virtual Type GetSerializableType(Type declaredType) + { + if (declaredType == null) + { + throw new ArgumentNullException(nameof(declaredType)); + } + + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( + new WrapperProviderContext(declaredType, isSerialization: false)); + + return wrapperProvider?.WrappingType ?? declaredType; + } + + /// + /// Called during deserialization to get the . + /// + /// The type of object for which the serializer should be created. + /// The used during deserialization. + protected virtual DataContractSerializer CreateSerializer(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + try + { + // If the serializer does not support this type it will throw an exception. + return new DataContractSerializer(type, _serializerSettings); + } + catch (Exception) + { + // We do not surface the caught exception because if CanRead returns + // false, then this Formatter is not picked up at all. + return null; + } + } + + /// + /// Gets the cached serializer or creates and caches the serializer for the given type. + /// + /// The instance. + protected virtual DataContractSerializer GetCachedSerializer(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!_serializerCache.TryGetValue(type, out var serializer)) + { + serializer = CreateSerializer(type); + if (serializer != null) + { + _serializerCache.TryAdd(type, serializer); + } + } + + return (DataContractSerializer)serializer; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs new file mode 100644 index 0000000000..a0f374cc27 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs @@ -0,0 +1,288 @@ +// Copyright (c) .NET 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.Collections.Generic; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using Microsoft.AspNetCore.Mvc.Formatters.Xml; +using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// This class handles serialization of objects + /// to XML using + /// + public class XmlDataContractSerializerOutputFormatter : TextOutputFormatter + { + private readonly ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); + private readonly ILogger _logger; + private DataContractSerializerSettings _serializerSettings; + + /// + /// Initializes a new instance of + /// with default . + /// + public XmlDataContractSerializerOutputFormatter() + : this(FormattingUtilities.GetDefaultXmlWriterSettings()) + { + } + + /// + /// Initializes a new instance of + /// with default . + /// + /// The . + public XmlDataContractSerializerOutputFormatter(ILoggerFactory loggerFactory) + : this(FormattingUtilities.GetDefaultXmlWriterSettings(), loggerFactory) + { + } + + /// + /// Initializes a new instance of . + /// + /// The settings to be used by the . + public XmlDataContractSerializerOutputFormatter(XmlWriterSettings writerSettings) + : this(writerSettings, loggerFactory: null) + { + } + + /// + /// Initializes a new instance of . + /// + /// The settings to be used by the . + /// The . + public XmlDataContractSerializerOutputFormatter(XmlWriterSettings writerSettings, ILoggerFactory loggerFactory) + { + if (writerSettings == null) + { + throw new ArgumentNullException(nameof(writerSettings)); + } + + SupportedEncodings.Add(Encoding.UTF8); + SupportedEncodings.Add(Encoding.Unicode); + + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml); + SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml); + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax); + + WriterSettings = writerSettings; + + _serializerSettings = new DataContractSerializerSettings(); + + WrapperProviderFactories = new List(); + WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories)); + WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); + + _logger = loggerFactory?.CreateLogger(GetType()); + } + + /// + /// Gets the list of to + /// provide the wrapping type for serialization. + /// + public IList WrapperProviderFactories { get; } + + /// + /// Gets the settings to be used by the XmlWriter. + /// + public XmlWriterSettings WriterSettings { get; } + + /// + /// Gets or sets the used to configure the + /// . + /// + public DataContractSerializerSettings SerializerSettings + { + get => _serializerSettings; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _serializerSettings = value; + } + } + + /// + /// Gets the type to be serialized. + /// + /// The original type to be serialized + /// The original or wrapped type provided by any s. + protected virtual Type GetSerializableType(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(new WrapperProviderContext( + type, + isSerialization: true)); + + return wrapperProvider?.WrappingType ?? type; + } + + /// + protected override bool CanWriteType(Type type) + { + if (type == null) + { + return false; + } + + return GetCachedSerializer(GetSerializableType(type)) != null; + } + + /// + /// Create a new instance of for the given object type. + /// + /// The type of object for which the serializer should be created. + /// A new instance of + protected virtual DataContractSerializer CreateSerializer(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + try + { + // Verify that type is a valid data contract by forcing the serializer to try to create a data contract + FormattingUtilities.XsdDataContractExporter.GetRootElementName(type); + + // If the serializer does not support this type it will throw an exception. + return new DataContractSerializer(type, _serializerSettings); + } + catch (Exception ex) + { + _logger?.FailedToCreateDataContractSerializer(type.FullName, ex); + + // We do not surface the caught exception because if CanWriteResult returns + // false, then this Formatter is not picked up at all. + return null; + } + } + + /// + /// Creates a new instance of using the given and + /// . + /// + /// + /// The underlying which the should write to. + /// + /// + /// The . + /// + /// A new instance of + public virtual XmlWriter CreateXmlWriter( + TextWriter writer, + XmlWriterSettings xmlWriterSettings) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + if (xmlWriterSettings == null) + { + throw new ArgumentNullException(nameof(xmlWriterSettings)); + } + + // We always close the TextWriter, so the XmlWriter shouldn't. + xmlWriterSettings.CloseOutput = false; + + return XmlWriter.Create(writer, xmlWriterSettings); + } + + /// + /// Creates a new instance of using the given and + /// . + /// + /// The formatter context associated with the call. + /// + /// The underlying which the should write to. + /// + /// + /// The . + /// + /// A new instance of . + public virtual XmlWriter CreateXmlWriter( + OutputFormatterWriteContext context, + TextWriter writer, + XmlWriterSettings xmlWriterSettings) + { + return CreateXmlWriter(writer, xmlWriterSettings); + } + + /// + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (selectedEncoding == null) + { + throw new ArgumentNullException(nameof(selectedEncoding)); + } + + var writerSettings = WriterSettings.Clone(); + writerSettings.Encoding = selectedEncoding; + + // Wrap the object only if there is a wrapping type. + var value = context.Object; + var wrappingType = GetSerializableType(context.ObjectType); + if (wrappingType != null && wrappingType != context.ObjectType) + { + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(new WrapperProviderContext( + declaredType: context.ObjectType, + isSerialization: true)); + + value = wrapperProvider.Wrap(value); + } + + var dataContractSerializer = GetCachedSerializer(wrappingType); + + using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding)) + { + using (var xmlWriter = CreateXmlWriter(context, textWriter, writerSettings)) + { + dataContractSerializer.WriteObject(xmlWriter, value); + } + + // 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 textWriter.FlushAsync(); + } + } + + /// + /// Gets the cached serializer or creates and caches the serializer for the given type. + /// + /// The instance. + protected virtual DataContractSerializer GetCachedSerializer(Type type) + { + if (!_serializerCache.TryGetValue(type, out var serializer)) + { + serializer = CreateSerializer(type); + if (serializer != null) + { + _serializerCache.TryAdd(type, serializer); + } + } + + return (DataContractSerializer)serializer; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs new file mode 100644 index 0000000000..6708c81257 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs @@ -0,0 +1,259 @@ +// Copyright (c) .NET 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Formatters.Xml; +using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.WebUtilities; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// This class handles deserialization of input XML data + /// to strongly-typed objects using + /// + public class XmlSerializerInputFormatter : TextInputFormatter, IInputFormatterExceptionPolicy + { + private readonly ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); + private readonly XmlDictionaryReaderQuotas _readerQuotas = FormattingUtilities.GetDefaultXmlReaderQuotas(); + private readonly bool _suppressInputFormatterBuffering; + private readonly MvcOptions _options; + + /// + /// Initializes a new instance of XmlSerializerInputFormatter. + /// + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public XmlSerializerInputFormatter() + { + SupportedEncodings.Add(UTF8EncodingWithoutBOM); + SupportedEncodings.Add(UTF16EncodingLittleEndian); + + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml); + SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml); + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax); + + WrapperProviderFactories = new List(); + WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); + } + + /// + /// Initializes a new instance of . + /// + /// Flag to buffer entire request body before deserializing it. + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public XmlSerializerInputFormatter(bool suppressInputFormatterBuffering) + : this() + { + _suppressInputFormatterBuffering = suppressInputFormatterBuffering; + } + + /// + /// Initializes a new instance of . + /// + /// The . + public XmlSerializerInputFormatter(MvcOptions options) +#pragma warning disable CS0618 + : this() +#pragma warning restore CS0618 + { + _options = options; + } + + /// + /// Gets the list of to + /// provide the wrapping type for de-serialization. + /// + public IList WrapperProviderFactories { get; } + + /// + /// Indicates the acceptable input XML depth. + /// + public int MaxDepth + { + get { return _readerQuotas.MaxDepth; } + set { _readerQuotas.MaxDepth = value; } + } + + /// + /// The quotas include - DefaultMaxDepth, DefaultMaxStringContentLength, DefaultMaxArrayLength, + /// DefaultMaxBytesPerRead, DefaultMaxNameTableCharCount + /// + public XmlDictionaryReaderQuotas XmlDictionaryReaderQuotas => _readerQuotas; + + /// + public virtual InputFormatterExceptionPolicy ExceptionPolicy + { + get + { + if (GetType() == typeof(XmlSerializerInputFormatter)) + { + return InputFormatterExceptionPolicy.MalformedInputExceptions; + } + return InputFormatterExceptionPolicy.AllExceptions; + } + } + + /// + public override async Task ReadRequestBodyAsync( + InputFormatterContext context, + Encoding encoding) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + var request = context.HttpContext.Request; + + var suppressInputFormatterBuffering = _options?.SuppressInputFormatterBuffering ?? _suppressInputFormatterBuffering; + + if (!request.Body.CanSeek && !suppressInputFormatterBuffering) + { + // XmlSerializer does synchronous reads. In order to avoid blocking on the stream, we asynchronously + // read everything into a buffer, and then seek back to the beginning. + request.EnableBuffering(); + Debug.Assert(request.Body.CanSeek); + + await request.Body.DrainAsync(CancellationToken.None); + request.Body.Seek(0L, SeekOrigin.Begin); + } + + try + { + using (var xmlReader = CreateXmlReader(new NonDisposableStream(request.Body), encoding)) + { + var type = GetSerializableType(context.ModelType); + + var serializer = GetCachedSerializer(type); + + var deserializedObject = serializer.Deserialize(xmlReader); + + // Unwrap only if the original type was wrapped. + if (type != context.ModelType) + { + if (deserializedObject is IUnwrappable unwrappable) + { + deserializedObject = unwrappable.Unwrap(declaredType: context.ModelType); + } + } + + return InputFormatterResult.Success(deserializedObject); + } + } + // XmlSerializer wraps actual exceptions (like FormatException or XmlException) into an InvalidOperationException + // https://github.com/dotnet/corefx/blob/master/src/System.Private.Xml/src/System/Xml/Serialization/XmlSerializer.cs#L652 + catch (InvalidOperationException exception) when (exception.InnerException is FormatException || exception.InnerException is XmlException) + { + throw new InputFormatterException(Resources.ErrorDeserializingInputData, exception); + } + } + + /// + protected override bool CanReadType(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + return GetCachedSerializer(GetSerializableType(type)) != null; + } + + /// + /// Gets the type to which the XML will be deserialized. + /// + /// The declared type. + /// The type to which the XML will be deserialized. + protected virtual Type GetSerializableType(Type declaredType) + { + if (declaredType == null) + { + throw new ArgumentNullException(nameof(declaredType)); + } + + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider( + new WrapperProviderContext(declaredType, isSerialization: false)); + + return wrapperProvider?.WrappingType ?? declaredType; + } + + /// + /// Called during deserialization to get the . + /// + /// The from which to read. + /// The used to read the stream. + /// The used during deserialization. + protected virtual XmlReader CreateXmlReader(Stream readStream, Encoding encoding) + { + if (readStream == null) + { + throw new ArgumentNullException(nameof(readStream)); + } + + if (encoding == null) + { + throw new ArgumentNullException(nameof(encoding)); + } + + return XmlDictionaryReader.CreateTextReader(readStream, encoding, _readerQuotas, onClose: null); + } + + /// + /// Called during deserialization to get the . + /// + /// The used during deserialization. + protected virtual XmlSerializer CreateSerializer(Type type) + { + try + { + // If the serializer does not support this type it will throw an exception. + return new XmlSerializer(type); + } + catch (Exception) + { + // We do not surface the caught exception because if CanRead returns + // false, then this Formatter is not picked up at all. + return null; + } + } + + /// + /// Gets the cached serializer or creates and caches the serializer for the given type. + /// + /// The instance. + protected virtual XmlSerializer GetCachedSerializer(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + if (!_serializerCache.TryGetValue(type, out var serializer)) + { + serializer = CreateSerializer(type); + if (serializer != null) + { + _serializerCache.TryAdd(type, serializer); + } + } + + return (XmlSerializer)serializer; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs new file mode 100644 index 0000000000..c71be18264 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs @@ -0,0 +1,276 @@ +// Copyright (c) .NET 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.Collections.Generic; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; +using Microsoft.AspNetCore.Mvc.Formatters.Xml; +using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Formatters +{ + /// + /// This class handles serialization of objects + /// to XML using + /// + public class XmlSerializerOutputFormatter : TextOutputFormatter + { + private readonly ConcurrentDictionary _serializerCache = new ConcurrentDictionary(); + private readonly ILogger _logger; + + /// + /// Initializes a new instance of + /// with default . + /// + public XmlSerializerOutputFormatter() + : this(FormattingUtilities.GetDefaultXmlWriterSettings()) + { + } + + /// + /// Initializes a new instance of + /// with default . + /// + /// The . + public XmlSerializerOutputFormatter(ILoggerFactory loggerFactory) + : this(FormattingUtilities.GetDefaultXmlWriterSettings(), loggerFactory) + { + } + + /// + /// Initializes a new instance of . + /// + /// The settings to be used by the . + public XmlSerializerOutputFormatter(XmlWriterSettings writerSettings) + : this(writerSettings, loggerFactory: null) + { + } + + /// + /// Initializes a new instance of + /// + /// The settings to be used by the . + /// The . + public XmlSerializerOutputFormatter(XmlWriterSettings writerSettings, ILoggerFactory loggerFactory) + { + if (writerSettings == null) + { + throw new ArgumentNullException(nameof(writerSettings)); + } + + SupportedEncodings.Add(Encoding.UTF8); + SupportedEncodings.Add(Encoding.Unicode); + + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationXml); + SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml); + SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax); + + WriterSettings = writerSettings; + + WrapperProviderFactories = new List(); + WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories)); + WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); + + _logger = loggerFactory?.CreateLogger(GetType()); + } + + /// + /// Gets the list of to + /// provide the wrapping type for serialization. + /// + public IList WrapperProviderFactories { get; } + + /// + /// Gets the settings to be used by the XmlWriter. + /// + public XmlWriterSettings WriterSettings { get; } + + /// + /// Gets the type to be serialized. + /// + /// The original type to be serialized + /// The original or wrapped type provided by any . + protected virtual Type GetSerializableType(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(new WrapperProviderContext( + type, + isSerialization: true)); + + return wrapperProvider?.WrappingType ?? type; + } + + /// + protected override bool CanWriteType(Type type) + { + if (type == null) + { + return false; + } + + return GetCachedSerializer(GetSerializableType(type)) != null; + } + + /// + /// Create a new instance of for the given object type. + /// + /// The type of object for which the serializer should be created. + /// A new instance of + protected virtual XmlSerializer CreateSerializer(Type type) + { + if (type == null) + { + throw new ArgumentNullException(nameof(type)); + } + + try + { + // If the serializer does not support this type it will throw an exception. + return new XmlSerializer(type); + } + catch (Exception ex) + { + _logger?.FailedToCreateXmlSerializer(type.FullName, ex); + + // We do not surface the caught exception because if CanWriteResult returns + // false, then this Formatter is not picked up at all. + return null; + } + } + + /// + /// Creates a new instance of using the given and + /// . + /// + /// + /// The underlying which the should write to. + /// + /// + /// The . + /// + /// A new instance of . + public virtual XmlWriter CreateXmlWriter( + TextWriter writer, + XmlWriterSettings xmlWriterSettings) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + if (xmlWriterSettings == null) + { + throw new ArgumentNullException(nameof(xmlWriterSettings)); + } + + // We always close the TextWriter, so the XmlWriter shouldn't. + xmlWriterSettings.CloseOutput = false; + + return XmlWriter.Create(writer, xmlWriterSettings); + } + + /// + /// Creates a new instance of using the given and + /// . + /// + /// The formatter context associated with the call. + /// + /// The underlying which the should write to. + /// + /// + /// The . + /// + /// A new instance of + public virtual XmlWriter CreateXmlWriter( + OutputFormatterWriteContext context, + TextWriter writer, + XmlWriterSettings xmlWriterSettings) + { + return CreateXmlWriter(writer, xmlWriterSettings); + } + + /// + public override async Task WriteResponseBodyAsync(OutputFormatterWriteContext context, Encoding selectedEncoding) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (selectedEncoding == null) + { + throw new ArgumentNullException(nameof(selectedEncoding)); + } + + var writerSettings = WriterSettings.Clone(); + writerSettings.Encoding = selectedEncoding; + + // Wrap the object only if there is a wrapping type. + var value = context.Object; + var wrappingType = GetSerializableType(context.ObjectType); + if (wrappingType != null && wrappingType != context.ObjectType) + { + var wrapperProvider = WrapperProviderFactories.GetWrapperProvider(new WrapperProviderContext( + declaredType: context.ObjectType, + isSerialization: true)); + + value = wrapperProvider.Wrap(value); + } + + var xmlSerializer = GetCachedSerializer(wrappingType); + + using (var textWriter = context.WriterFactory(context.HttpContext.Response.Body, writerSettings.Encoding)) + { + using (var xmlWriter = CreateXmlWriter(context, textWriter, writerSettings)) + { + Serialize(xmlSerializer, xmlWriter, value); + } + + // 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 textWriter.FlushAsync(); + } + } + + /// + /// Serializes value using the passed in and . + /// + /// The serializer used to serialize the . + /// The writer used by the serializer + /// to serialize the . + /// The value to be serialized. + protected virtual void Serialize(XmlSerializer xmlSerializer, XmlWriter xmlWriter, object value) + { + xmlSerializer.Serialize(xmlWriter, value); + } + + /// + /// Gets the cached serializer or creates and caches the serializer for the given type. + /// + /// The instance. + protected virtual XmlSerializer GetCachedSerializer(Type type) + { + if (!_serializerCache.TryGetValue(type, out var serializer)) + { + serializer = CreateSerializer(type); + if (serializer != null) + { + _serializerCache.TryAdd(type, serializer); + } + } + + return (XmlSerializer)serializer; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/baseline.netcore.json new file mode 100644 index 0000000000..e617a3e461 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/baseline.netcore.json @@ -0,0 +1,1500 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Formatters.Xml, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DataMemberRequiredBindingMetadataProvider", + "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.Formatters.XmlDataContractSerializerInputFormatter", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_WrapperProviderFactories", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MaxDepth", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MaxDepth", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_XmlDictionaryReaderQuotas", + "Parameters": [], + "ReturnType": "System.Xml.XmlDictionaryReaderQuotas", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SerializerSettings", + "Parameters": [], + "ReturnType": "System.Runtime.Serialization.DataContractSerializerSettings", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SerializerSettings", + "Parameters": [ + { + "Name": "value", + "Type": "System.Runtime.Serialization.DataContractSerializerSettings" + } + ], + "ReturnType": "System.Void", + "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", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CanReadType", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateXmlReader", + "Parameters": [ + { + "Name": "readStream", + "Type": "System.IO.Stream" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.Xml.XmlReader", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetSerializableType", + "Parameters": [ + { + "Name": "declaredType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Type", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateSerializer", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Runtime.Serialization.DataContractSerializer", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetCachedSerializer", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Runtime.Serialization.DataContractSerializer", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "suppressInputFormatterBuffering", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.XmlDataContractSerializerOutputFormatter", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_WrapperProviderFactories", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_WriterSettings", + "Parameters": [], + "ReturnType": "System.Xml.XmlWriterSettings", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SerializerSettings", + "Parameters": [], + "ReturnType": "System.Runtime.Serialization.DataContractSerializerSettings", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SerializerSettings", + "Parameters": [ + { + "Name": "value", + "Type": "System.Runtime.Serialization.DataContractSerializerSettings" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetSerializableType", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Type", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CanWriteType", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateSerializer", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Runtime.Serialization.DataContractSerializer", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateXmlWriter", + "Parameters": [ + { + "Name": "writer", + "Type": "System.IO.TextWriter" + }, + { + "Name": "xmlWriterSettings", + "Type": "System.Xml.XmlWriterSettings" + } + ], + "ReturnType": "System.Xml.XmlWriter", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + }, + { + "Name": "selectedEncoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetCachedSerializer", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Runtime.Serialization.DataContractSerializer", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "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": "writerSettings", + "Type": "System.Xml.XmlWriterSettings" + } + ], + "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerInputFormatter", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_WrapperProviderFactories", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MaxDepth", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MaxDepth", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_XmlDictionaryReaderQuotas", + "Parameters": [], + "ReturnType": "System.Xml.XmlDictionaryReaderQuotas", + "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", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CanReadType", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetSerializableType", + "Parameters": [ + { + "Name": "declaredType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Type", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateXmlReader", + "Parameters": [ + { + "Name": "readStream", + "Type": "System.IO.Stream" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.Xml.XmlReader", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateSerializer", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Xml.Serialization.XmlSerializer", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetCachedSerializer", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Xml.Serialization.XmlSerializer", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "suppressInputFormatterBuffering", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.XmlSerializerOutputFormatter", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.TextOutputFormatter", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_WrapperProviderFactories", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_WriterSettings", + "Parameters": [], + "ReturnType": "System.Xml.XmlWriterSettings", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetSerializableType", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Type", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CanWriteType", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateSerializer", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Xml.Serialization.XmlSerializer", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateXmlWriter", + "Parameters": [ + { + "Name": "writer", + "Type": "System.IO.TextWriter" + }, + { + "Name": "xmlWriterSettings", + "Type": "System.Xml.XmlWriterSettings" + } + ], + "ReturnType": "System.Xml.XmlWriter", + "Virtual": true, + "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", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + }, + { + "Name": "selectedEncoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "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", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + } + ], + "ReturnType": "System.Xml.Serialization.XmlSerializer", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "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": "writerSettings", + "Type": "System.Xml.XmlWriterSettings" + } + ], + "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.DelegatingEnumerable", + "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": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "item", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "source", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "elementWrapperProvider", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TWrapped", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + }, + { + "ParameterName": "TDeclared", + "ParameterPosition": 1, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.DelegatingEnumerator", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerator" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Dispose", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.IDisposable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MoveNext", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.IEnumerator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Reset", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.IEnumerator", + "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", + "Parameters": [ + { + "Name": "inner", + "Type": "System.Collections.Generic.IEnumerator" + }, + { + "Name": "wrapperProvider", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TWrapped", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + }, + { + "ParameterName": "TDeclared", + "ParameterPosition": 1, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.EnumerableWrapperProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_WrappingType", + "Parameters": [], + "ReturnType": "System.Type", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Wrap", + "Parameters": [ + { + "Name": "original", + "Type": "System.Object" + } + ], + "ReturnType": "System.Object", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "sourceEnumerableOfT", + "Type": "System.Type" + }, + { + "Name": "elementWrapperProvider", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.EnumerableWrapperProviderFactory", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProviderFactory" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetProvider", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.Xml.WrapperProviderContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProviderFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "wrapperProviderFactories", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IUnwrappable", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Unwrap", + "Parameters": [ + { + "Name": "declaredType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Object", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_WrappingType", + "Parameters": [], + "ReturnType": "System.Type", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Wrap", + "Parameters": [ + { + "Name": "original", + "Type": "System.Object" + } + ], + "ReturnType": "System.Object", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProviderFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetProvider", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.Xml.WrapperProviderContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.SerializableErrorWrapper", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "ImplementedInterfaces": [ + "System.Xml.Serialization.IXmlSerializable", + "Microsoft.AspNetCore.Mvc.Formatters.Xml.IUnwrappable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_SerializableError", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.SerializableError", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetSchema", + "Parameters": [], + "ReturnType": "System.Xml.Schema.XmlSchema", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Xml.Serialization.IXmlSerializable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReadXml", + "Parameters": [ + { + "Name": "reader", + "Type": "System.Xml.XmlReader" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Xml.Serialization.IXmlSerializable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteXml", + "Parameters": [ + { + "Name": "writer", + "Type": "System.Xml.XmlWriter" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Xml.Serialization.IXmlSerializable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Unwrap", + "Parameters": [ + { + "Name": "declaredType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Object", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IUnwrappable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "error", + "Type": "Microsoft.AspNetCore.Mvc.SerializableError" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.SerializableErrorWrapperProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_WrappingType", + "Parameters": [], + "ReturnType": "System.Type", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Wrap", + "Parameters": [ + { + "Name": "original", + "Type": "System.Object" + } + ], + "ReturnType": "System.Object", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.SerializableErrorWrapperProviderFactory", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProviderFactory" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetProvider", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.Xml.WrapperProviderContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProviderFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.WrapperProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_DeclaredType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsSerialization", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "declaredType", + "Type": "System.Type" + }, + { + "Name": "isSerialization", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.WrapperProviderFactoriesExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetWrapperProvider", + "Parameters": [ + { + "Name": "wrapperProviderFactories", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "wrapperProviderContext", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.Xml.WrapperProviderContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.Xml.IWrapperProvider", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcXmlMvcBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddXmlDataContractSerializerFormatters", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddXmlSerializerFormatters", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcXmlMvcCoreBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddXmlDataContractSerializerFormatters", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddXmlSerializerFormatters", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcBuilderExtensions.cs new file mode 100644 index 0000000000..ddeb4f25b4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcBuilderExtensions.cs @@ -0,0 +1,319 @@ +// Copyright (c) .NET 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.DataAnnotations; +using Microsoft.AspNetCore.Mvc.Localization.Internal; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.Extensions.Localization; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for configuring MVC view and data annotations localization services. + /// + public static class MvcLocalizationMvcBuilderExtensions + { + /// + /// Adds MVC view localization services to the application. + /// + /// The . + /// The . + public static IMvcBuilder AddViewLocalization(this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddViewLocalization(builder, LanguageViewLocationExpanderFormat.Suffix); + } + + /// + /// Adds MVC view localization services to the application. + /// + /// The . + /// The view format for localized views. + /// The . + public static IMvcBuilder AddViewLocalization( + this IMvcBuilder builder, + LanguageViewLocationExpanderFormat format) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddViewLocalization(builder, format, setupAction: null); + return builder; + } + + /// + /// Adds MVC view localization services to the application. + /// + /// The . + /// An action to configure the . + /// The . + public static IMvcBuilder AddViewLocalization( + this IMvcBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddViewLocalization(builder, LanguageViewLocationExpanderFormat.Suffix, setupAction); + return builder; + } + + /// + /// Adds MVC view localization services to the application. + /// + /// The . + /// The view format for localized views. + /// An action to configure the . + /// The . + public static IMvcBuilder AddViewLocalization( + this IMvcBuilder builder, + LanguageViewLocationExpanderFormat format, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + MvcLocalizationServices.AddLocalizationServices(builder.Services, format, setupAction); + return builder; + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcBuilder AddMvcLocalization(this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: null, + format: LanguageViewLocationExpanderFormat.Suffix, + dataAnnotationsLocalizationOptionsSetupAction: null); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// An action to configure the . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcBuilder AddMvcLocalization( + this IMvcBuilder builder, + Action localizationOptionsSetupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction, + LanguageViewLocationExpanderFormat.Suffix, + dataAnnotationsLocalizationOptionsSetupAction: null); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// The view format for localized views. + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcBuilder AddMvcLocalization( + this IMvcBuilder builder, + LanguageViewLocationExpanderFormat format) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: null, + format: format, + dataAnnotationsLocalizationOptionsSetupAction: null); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// An action to configure the + /// . + /// The view format for localized views. + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcBuilder AddMvcLocalization( + this IMvcBuilder builder, + Action localizationOptionsSetupAction, + LanguageViewLocationExpanderFormat format) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: localizationOptionsSetupAction, + format: format, + dataAnnotationsLocalizationOptionsSetupAction: null); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// An action to configure the + /// . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcBuilder AddMvcLocalization( + this IMvcBuilder builder, + Action dataAnnotationsLocalizationOptionsSetupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: null, + format: LanguageViewLocationExpanderFormat.Suffix, + dataAnnotationsLocalizationOptionsSetupAction: dataAnnotationsLocalizationOptionsSetupAction); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// An action to configure the + /// . + /// An action to configure the + /// . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcBuilder AddMvcLocalization( + this IMvcBuilder builder, + Action localizationOptionsSetupAction, + Action dataAnnotationsLocalizationOptionsSetupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: localizationOptionsSetupAction, + format: LanguageViewLocationExpanderFormat.Suffix, + dataAnnotationsLocalizationOptionsSetupAction: dataAnnotationsLocalizationOptionsSetupAction); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// The view format for localized views. + /// An action to configure the + /// . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcBuilder AddMvcLocalization( + this IMvcBuilder builder, + LanguageViewLocationExpanderFormat format, + Action dataAnnotationsLocalizationOptionsSetupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: null, + format: format, + dataAnnotationsLocalizationOptionsSetupAction: dataAnnotationsLocalizationOptionsSetupAction); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// An action to configure the . + /// Can be null. + /// The view format for localized views. + /// An action to configure + /// the . Can be null. + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcBuilder AddMvcLocalization( + this IMvcBuilder builder, + Action localizationOptionsSetupAction, + LanguageViewLocationExpanderFormat format, + Action dataAnnotationsLocalizationOptionsSetupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder + .AddViewLocalization(format, localizationOptionsSetupAction) + .AddDataAnnotationsLocalization(dataAnnotationsLocalizationOptionsSetupAction); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcCoreBuilderExtensions.cs new file mode 100644 index 0000000000..27b1f90a7f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcCoreBuilderExtensions.cs @@ -0,0 +1,344 @@ +// Copyright (c) .NET 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.DataAnnotations; +using Microsoft.AspNetCore.Mvc.Localization.Internal; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.Extensions.Localization; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for configuring MVC view and data annotations localization services. + /// + public static class MvcLocalizationMvcCoreBuilderExtensions + { + /// + /// Adds MVC view localization services to the application. + /// + /// The . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddViewLocalization(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddViewLocalization(builder, LanguageViewLocationExpanderFormat.Suffix); + } + + /// + /// Adds MVC view localization services to the application. + /// + /// The . + /// The view format for localized views. + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddViewLocalization( + this IMvcCoreBuilder builder, + LanguageViewLocationExpanderFormat format) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddViews(); + builder.AddRazorViewEngine(); + + MvcLocalizationServices.AddLocalizationServices(builder.Services, format, setupAction: null); + return builder; + } + + /// + /// Adds MVC view localization services to the application. + /// + /// The . + /// An action to configure the . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddViewLocalization( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddViewLocalization(builder, LanguageViewLocationExpanderFormat.Suffix, setupAction); + } + + /// + /// Adds MVC view localization services to the application. + /// + /// The . + /// The view format for localized views. + /// An action to configure the . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddViewLocalization( + this IMvcCoreBuilder builder, + LanguageViewLocationExpanderFormat format, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddViews(); + builder.AddRazorViewEngine(); + + MvcLocalizationServices.AddLocalizationServices(builder.Services, format, setupAction); + return builder; + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddMvcLocalization(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: null, + format: LanguageViewLocationExpanderFormat.Suffix, + dataAnnotationsLocalizationOptionsSetupAction: null); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// An action to configure the . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddMvcLocalization( + this IMvcCoreBuilder builder, + Action localizationOptionsSetupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction, + LanguageViewLocationExpanderFormat.Suffix, + dataAnnotationsLocalizationOptionsSetupAction: null); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// The view format for localized views. + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddMvcLocalization( + this IMvcCoreBuilder builder, + LanguageViewLocationExpanderFormat format) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: null, + format: format, + dataAnnotationsLocalizationOptionsSetupAction: null); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// An action to configure the + /// . + /// The view format for localized views. + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddMvcLocalization( + this IMvcCoreBuilder builder, + Action localizationOptionsSetupAction, + LanguageViewLocationExpanderFormat format) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: localizationOptionsSetupAction, + format: format, + dataAnnotationsLocalizationOptionsSetupAction: null); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// An action to configure + /// the . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddMvcLocalization( + this IMvcCoreBuilder builder, + Action dataAnnotationsLocalizationOptionsSetupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: null, + format: LanguageViewLocationExpanderFormat.Suffix, + dataAnnotationsLocalizationOptionsSetupAction: dataAnnotationsLocalizationOptionsSetupAction); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// An action to configure the + /// . + /// An action to configure the + /// . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddMvcLocalization( + this IMvcCoreBuilder builder, + Action localizationOptionsSetupAction, + Action dataAnnotationsLocalizationOptionsSetupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: localizationOptionsSetupAction, + format: LanguageViewLocationExpanderFormat.Suffix, + dataAnnotationsLocalizationOptionsSetupAction: dataAnnotationsLocalizationOptionsSetupAction); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// The view format for localized views. + /// An action to configure the + /// . + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddMvcLocalization( + this IMvcCoreBuilder builder, + LanguageViewLocationExpanderFormat format, + Action dataAnnotationsLocalizationOptionsSetupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return AddMvcLocalization( + builder, + localizationOptionsSetupAction: null, + format: format, + dataAnnotationsLocalizationOptionsSetupAction: dataAnnotationsLocalizationOptionsSetupAction); + } + + /// + /// Adds MVC view and data annotations localization services to the application. + /// + /// The . + /// An action to configure + /// the . Can be null. + /// The view format for localized views. + /// An action to configure + /// the . Can be null. + /// The . + /// + /// Adding localization also adds support for views via + /// and the Razor view engine + /// via . + /// + public static IMvcCoreBuilder AddMvcLocalization( + this IMvcCoreBuilder builder, + Action localizationOptionsSetupAction, + LanguageViewLocationExpanderFormat format, + Action dataAnnotationsLocalizationOptionsSetupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + return builder + .AddViewLocalization(format, localizationOptionsSetupAction) + .AddDataAnnotationsLocalization(dataAnnotationsLocalizationOptionsSetupAction); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizer.cs new file mode 100644 index 0000000000..9ef531438f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizer.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET 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.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.Localization +{ + /// + /// An that uses the provided to do HTML-aware + /// localization of content. + /// + public class HtmlLocalizer : IHtmlLocalizer + { + private readonly IStringLocalizer _localizer; + + /// + /// Creates a new . + /// + /// The to read strings from. + public HtmlLocalizer(IStringLocalizer localizer) + { + if (localizer == null) + { + throw new ArgumentNullException(nameof(localizer)); + } + + _localizer = localizer; + } + + /// + public virtual LocalizedHtmlString this[string name] + { + get + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return ToHtmlString(_localizer[name]); + } + } + + /// + public virtual LocalizedHtmlString this[string name, params object[] arguments] + { + get + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return ToHtmlString(_localizer[name], arguments); + } + } + + /// + public virtual LocalizedString GetString(string name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return _localizer[name]; + } + + /// + public virtual LocalizedString GetString(string name, params object[] arguments) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return _localizer[name, arguments]; + } + + /// + public virtual IEnumerable GetAllStrings(bool includeParentCultures) => + _localizer.GetAllStrings(includeParentCultures); + + /// + public virtual IHtmlLocalizer WithCulture(CultureInfo culture) + { + if (culture == null) + { + throw new ArgumentNullException(nameof(culture)); + } + + return new HtmlLocalizer(_localizer.WithCulture(culture)); + } + + /// + /// Creates a new for a . + /// + /// The . + protected virtual LocalizedHtmlString ToHtmlString(LocalizedString result) => + new LocalizedHtmlString(result.Name, result.Value, result.ResourceNotFound); + + protected virtual LocalizedHtmlString ToHtmlString(LocalizedString result, object[] arguments) => + new LocalizedHtmlString(result.Name, result.Value, result.ResourceNotFound, arguments); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerExtensions.cs new file mode 100644 index 0000000000..30a463c9f6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerExtensions.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 System; +using System.Collections.Generic; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.Localization +{ + /// + /// Extension methods for . + /// + public static class HtmlLocalizerExtensions + { + /// + /// Gets the resource for a specific name. + /// + /// The . + /// The key to use. + /// The resource. + public static LocalizedHtmlString GetHtml(this IHtmlLocalizer htmlLocalizer, string name) + { + if (htmlLocalizer == null) + { + throw new ArgumentNullException(nameof(htmlLocalizer)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return htmlLocalizer[name]; + } + + /// + /// Gets the resource for a specific name. + /// + /// The . + /// The key to use. + /// The values to format the string with. + /// The resource. + public static LocalizedHtmlString GetHtml(this IHtmlLocalizer htmlLocalizer, string name, params object[] arguments) + { + if (htmlLocalizer == null) + { + throw new ArgumentNullException(nameof(htmlLocalizer)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return htmlLocalizer[name, arguments]; + } + + /// + /// Gets all string resources including those for parent cultures. + /// + /// The . + /// The string resources. + public static IEnumerable GetAllStrings(this IHtmlLocalizer htmlLocalizer) + { + if (htmlLocalizer == null) + { + throw new ArgumentNullException(nameof(htmlLocalizer)); + } + + return htmlLocalizer.GetAllStrings(includeParentCultures: true); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerFactory.cs new file mode 100644 index 0000000000..02684f1e4f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerFactory.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 Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.Localization +{ + /// + /// An that creates instances of using the + /// registered . + /// + public class HtmlLocalizerFactory : IHtmlLocalizerFactory + { + private readonly IStringLocalizerFactory _factory; + + /// + /// Creates a new . + /// + /// The . + public HtmlLocalizerFactory(IStringLocalizerFactory localizerFactory) + { + if (localizerFactory == null) + { + throw new ArgumentNullException(nameof(localizerFactory)); + } + + _factory = localizerFactory; + } + + /// + /// Creates an using the specified . + /// + /// The to load resources for. + /// The . + public virtual IHtmlLocalizer Create(Type resourceSource) + { + if (resourceSource == null) + { + throw new ArgumentNullException(nameof(resourceSource)); + } + + return new HtmlLocalizer(_factory.Create(resourceSource)); + } + + /// + /// Creates an using the specified base name and location. + /// + /// The base name of the resource to load strings from. + /// The location to load resources from. + /// The . + public virtual IHtmlLocalizer Create(string baseName, string location) + { + if (baseName == null) + { + throw new ArgumentNullException(nameof(baseName)); + } + + if (location == null) + { + throw new ArgumentNullException(nameof(location)); + } + + var localizer = _factory.Create(baseName, location); + return new HtmlLocalizer(localizer); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerOfT.cs new file mode 100644 index 0000000000..cea9a40a1c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerOfT.cs @@ -0,0 +1,94 @@ +// Copyright (c) .NET 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.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.Localization +{ + /// + /// An implementation that provides localized HTML content for the specified type + /// . + /// + /// The to scope the resource names. + public class HtmlLocalizer : IHtmlLocalizer + { + private readonly IHtmlLocalizer _localizer; + + /// + /// Creates a new . + /// + /// The . + public HtmlLocalizer(IHtmlLocalizerFactory factory) + { + _localizer = factory.Create(typeof(TResource)); + } + + /// + public virtual LocalizedHtmlString this[string name] + { + get + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return _localizer[name]; + } + } + + /// + public virtual LocalizedHtmlString this[string name, params object[] arguments] + { + get + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return _localizer[name, arguments]; + } + } + + /// + public virtual LocalizedString GetString(string name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return _localizer.GetString(name); + } + + /// + public virtual LocalizedString GetString(string name, params object[] arguments) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return _localizer.GetString(name, arguments); + } + + /// + public virtual IEnumerable GetAllStrings(bool includeParentCultures) => + _localizer.GetAllStrings(includeParentCultures); + + /// + public virtual IHtmlLocalizer WithCulture(CultureInfo culture) + { + if (culture == null) + { + throw new ArgumentNullException(nameof(culture)); + } + + return _localizer.WithCulture(culture); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizer.cs new file mode 100644 index 0000000000..f9589a990c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizer.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.Collections.Generic; +using System.Globalization; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.Localization +{ + /// + /// Represents a type that does HTML-aware localization of strings, by HTML encoding arguments that are + /// formatted in the resource string. + /// + public interface IHtmlLocalizer + { + /// + /// Gets the string resource with the given name. + /// + /// The name of the string resource. + /// The string resource as a . + LocalizedHtmlString this[string name] { get; } + + /// + /// Gets the string resource with the given name and formatted with the supplied arguments. The arguments will + /// be HTML encoded. + /// + /// The name of the string resource. + /// The values to format the string with. + /// The formatted string resource as a . + LocalizedHtmlString this[string name, params object[] arguments] { get; } + + /// + /// Gets the string resource with the given name. + /// + /// The name of the string resource. + /// The string resource as a . + LocalizedString GetString(string name); + + /// + /// Gets the string resource with the given name and formatted with the supplied arguments. + /// + /// The name of the string resource. + /// The values to format the string with. + /// The formatted string resource as a . + LocalizedString GetString(string name, params object[] arguments); + + /// + /// Gets all string resources. + /// + /// + /// A indicating whether to include strings from parent cultures. + /// + /// The strings. + IEnumerable GetAllStrings(bool includeParentCultures); + + /// + /// Creates a new for a specific . + /// + /// The to use. + /// A culture-specific . + IHtmlLocalizer WithCulture(CultureInfo culture); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerFactory.cs new file mode 100644 index 0000000000..3e26add97d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerFactory.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; + +namespace Microsoft.AspNetCore.Mvc.Localization +{ + /// + /// A factory that creates instances. + /// + public interface IHtmlLocalizerFactory + { + /// + /// Creates an using the and + /// of the specified . + /// + /// The . + /// The . + IHtmlLocalizer Create(Type resourceSource); + + /// + /// Creates an . + /// + /// The base name of the resource to load strings from. + /// The location to load resources from. + /// The . + IHtmlLocalizer Create(string baseName, string location); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerOfT.cs new file mode 100644 index 0000000000..923de5ab89 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerOfT.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.Localization +{ + /// + /// An that provides localized HTML content. + /// + /// The to scope the resource names. + public interface IHtmlLocalizer : IHtmlLocalizer + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IViewLocalizer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IViewLocalizer.cs new file mode 100644 index 0000000000..63f1db6856 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IViewLocalizer.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.Localization +{ + /// + /// Represents a type that provides HTML-aware localization for views. + /// + public interface IViewLocalizer : IHtmlLocalizer + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Internal/MvcLocalizationServices.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Internal/MvcLocalizationServices.cs new file mode 100644 index 0000000000..4e1f26ebc0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Internal/MvcLocalizationServices.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 Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.Localization.Internal +{ + public static class MvcLocalizationServices + { + public static void AddLocalizationServices( + IServiceCollection services, + LanguageViewLocationExpanderFormat format, + Action setupAction) + { + AddMvcViewLocalizationServices(services, format, setupAction); + + if (setupAction == null) + { + services.AddLocalization(); + } + else + { + services.AddLocalization(setupAction); + } + } + + // To enable unit testing only 'MVC' specific services + public static void AddMvcViewLocalizationServices( + IServiceCollection services, + LanguageViewLocationExpanderFormat format, + Action setupAction) + { + services.Configure( + options => + { + options.ViewLocationExpanders.Add(new LanguageViewLocationExpander(format)); + }); + + services.TryAdd(ServiceDescriptor.Singleton()); + services.TryAdd(ServiceDescriptor.Transient(typeof(IHtmlLocalizer<>), typeof(HtmlLocalizer<>))); + services.TryAdd(ServiceDescriptor.Transient()); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/LocalizedHtmlString.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/LocalizedHtmlString.cs new file mode 100644 index 0000000000..ed91460d69 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/LocalizedHtmlString.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.IO; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Html; + +namespace Microsoft.AspNetCore.Mvc.Localization +{ + /// + /// An with localized content. + /// + public class LocalizedHtmlString : IHtmlContent + { + private readonly object[] _arguments; + + /// + /// Creates an instance of . + /// + /// The name of the string resource. + /// The string resource. + public LocalizedHtmlString(string name, string value) + : this(name, value, isResourceNotFound: false, arguments: Array.Empty()) + { + } + + /// + /// Creates an instance of . + /// + /// The name of the string resource. + /// The string resource. + /// A flag that indicates if the resource is not found. + public LocalizedHtmlString(string name, string value, bool isResourceNotFound) + : this(name, value, isResourceNotFound, arguments: Array.Empty()) + { + } + + /// + /// Creates an instance of . + /// + /// The name of the string resource. + /// The string resource. + /// A flag that indicates if the resource is not found. + /// The values to format the with. + public LocalizedHtmlString(string name, string value, bool isResourceNotFound, params object[] arguments) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + if (arguments == null) + { + throw new ArgumentNullException(nameof(arguments)); + } + + Name = name; + Value = value; + IsResourceNotFound = isResourceNotFound; + _arguments = arguments; + } + + /// + /// The name of the string resource. + /// + public string Name { get; } + + /// + /// The string resource. + /// + public string Value { get; } + + /// + /// Gets a flag that indicates if the resource is not found. + /// + public bool IsResourceNotFound { get; } + + /// + public void WriteTo(TextWriter writer, HtmlEncoder encoder) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + if (encoder == null) + { + throw new ArgumentNullException(nameof(encoder)); + } + + var formattableString = new HtmlFormattableString(Value, _arguments); + formattableString.WriteTo(writer, encoder); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj new file mode 100644 index 0000000000..ea50d25312 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj @@ -0,0 +1,23 @@ + + + + ASP.NET Core MVC features that enable globalization and localization of applications. +Commonly used types: +Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer<TResource> +Microsoft.AspNetCore.Mvc.Localization.IViewLocalizer + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;aspnetcoremvc;localization + + + + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..caa20abd18 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Properties/Resources.Designer.cs @@ -0,0 +1,62 @@ +// +namespace Microsoft.AspNetCore.Mvc.Localization +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Mvc.Localization.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// Must call CreateStringLocalizer method before using this property. + /// + internal static string NullStringLocalizer + { + get { return GetString("NullStringLocalizer"); } + } + + /// + /// Must call CreateStringLocalizer method before using this property. + /// + internal static string FormatNullStringLocalizer() + { + return GetString("NullStringLocalizer"); + } + + /// + /// IStringLocalizerFactory is null. Must call other constructor overload to use this property. + /// + internal static string NullStringLocalizerFactory + { + get { return GetString("NullStringLocalizerFactory"); } + } + + /// + /// IStringLocalizerFactory is null. Must call other constructor overload to use this property. + /// + internal static string FormatNullStringLocalizerFactory() + { + return GetString("NullStringLocalizerFactory"); + } + + 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/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/ViewLocalizer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/ViewLocalizer.cs new file mode 100644 index 0000000000..3ec50fe965 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/ViewLocalizer.cs @@ -0,0 +1,130 @@ +// Copyright (c) .NET 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.Globalization; +using System.IO; +using System.Text; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.Extensions.Localization; + +namespace Microsoft.AspNetCore.Mvc.Localization +{ + /// + /// An implementation that derives the resource location from the executing view's + /// file path. + /// + public class ViewLocalizer : IViewLocalizer, IViewContextAware + { + private readonly IHtmlLocalizerFactory _localizerFactory; + private readonly string _applicationName; + private IHtmlLocalizer _localizer; + + /// + /// Creates a new . + /// + /// The . + /// The . + public ViewLocalizer(IHtmlLocalizerFactory localizerFactory, IHostingEnvironment hostingEnvironment) + { + if (localizerFactory == null) + { + throw new ArgumentNullException(nameof(localizerFactory)); + } + + if (hostingEnvironment == null) + { + throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + _applicationName = hostingEnvironment.ApplicationName; + _localizerFactory = localizerFactory; + } + + /// + public virtual LocalizedHtmlString this[string key] + { + get + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + return _localizer[key]; + } + } + + /// + public virtual LocalizedHtmlString this[string key, params object[] arguments] + { + get + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + return _localizer[key, arguments]; + } + } + + /// + public LocalizedString GetString(string name) => _localizer.GetString(name); + + /// + public LocalizedString GetString(string name, params object[] values) => _localizer.GetString(name, values); + + /// + public IHtmlLocalizer WithCulture(CultureInfo culture) => _localizer.WithCulture(culture); + + /// + public IEnumerable GetAllStrings(bool includeParentCultures) => + _localizer.GetAllStrings(includeParentCultures); + + /// + /// Apply the specified . + /// + /// The . + public void Contextualize(ViewContext viewContext) + { + if (viewContext == null) + { + throw new ArgumentNullException(nameof(viewContext)); + } + + // Given a view path "/Views/Home/Index.cshtml" we want a baseName like "MyApplication.Views.Home.Index" + var path = viewContext.ExecutingFilePath; + + if (string.IsNullOrEmpty(path)) + { + path = viewContext.View.Path; + } + + Debug.Assert(!string.IsNullOrEmpty(path), "Couldn't determine a path for the view"); + + _localizer = _localizerFactory.Create(BuildBaseName(path), _applicationName); + } + + private string BuildBaseName(string path) + { + var extension = Path.GetExtension(path); + var startIndex = path[0] == '/' || path[0] == '\\' ? 1 : 0; + var length = path.Length - startIndex - extension.Length; + var capacity = length + _applicationName.Length + 1; + var builder = new StringBuilder(path, startIndex, length, capacity); + + builder.Replace('/', '.').Replace('\\', '.'); + + // Prepend the application name + builder.Insert(0, '.'); + builder.Insert(0, _applicationName); + + return builder.ToString(); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/baseline.netcore.json new file mode 100644 index 0000000000..196aed9900 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/baseline.netcore.json @@ -0,0 +1,1350 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Localization, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.Mvc.Localization.HtmlLocalizer", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetString", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetString", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAllStrings", + "Parameters": [ + { + "Name": "includeParentCultures", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WithCulture", + "Parameters": [ + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ToHtmlString", + "Parameters": [ + { + "Name": "result", + "Type": "Microsoft.Extensions.Localization.LocalizedString" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ToHtmlString", + "Parameters": [ + { + "Name": "result", + "Type": "Microsoft.Extensions.Localization.LocalizedString" + }, + { + "Name": "arguments", + "Type": "System.Object[]" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "localizer", + "Type": "Microsoft.Extensions.Localization.IStringLocalizer" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Localization.HtmlLocalizerExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetHtml", + "Parameters": [ + { + "Name": "htmlLocalizer", + "Type": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer" + }, + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetHtml", + "Parameters": [ + { + "Name": "htmlLocalizer", + "Type": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAllStrings", + "Parameters": [ + { + "Name": "htmlLocalizer", + "Type": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Localization.HtmlLocalizerFactory", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizerFactory" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "resourceSource", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizerFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "baseName", + "Type": "System.String" + }, + { + "Name": "location", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizerFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "localizerFactory", + "Type": "Microsoft.Extensions.Localization.IStringLocalizerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Localization.HtmlLocalizer", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetString", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetString", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAllStrings", + "Parameters": [ + { + "Name": "includeParentCultures", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WithCulture", + "Parameters": [ + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "factory", + "Type": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TResource", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetString", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetString", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAllStrings", + "Parameters": [ + { + "Name": "includeParentCultures", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WithCulture", + "Parameters": [ + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizerFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "resourceSource", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "baseName", + "Type": "System.String" + }, + { + "Name": "location", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer" + ], + "Members": [], + "GenericParameters": [ + { + "ParameterName": "TResource", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Localization.IViewLocalizer", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer" + ], + "Members": [], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Html.IHtmlContent" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Value", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsResourceNotFound", + "Parameters": [], + "ReturnType": "System.Boolean", + "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": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.String" + }, + { + "Name": "isResourceNotFound", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.String" + }, + { + "Name": "isResourceNotFound", + "Type": "System.Boolean" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Localization.ViewLocalizer", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Localization.IViewLocalizer", + "Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "arguments", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.LocalizedHtmlString", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetString", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetString", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "values", + "Type": "System.Object[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.Extensions.Localization.LocalizedString", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAllStrings", + "Parameters": [ + { + "Name": "includeParentCultures", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "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", + "Parameters": [ + { + "Name": "viewContext", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewFeatures.IViewContextAware", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "localizerFactory", + "Type": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizerFactory" + }, + { + "Name": "hostingEnvironment", + "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcLocalizationMvcBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddViewLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddViewLocalization", + "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": "AddViewLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddViewLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "format", + "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" + }, + { + "Name": "setupAction", + "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" + } + ], + "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": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcLocalizationMvcCoreBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddViewLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddViewLocalization", + "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": "AddViewLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddViewLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "format", + "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" + }, + { + "Name": "setupAction", + "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" + } + ], + "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": [] + } + ] +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyApplicationPartFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyApplicationPartFactory.cs new file mode 100644 index 0000000000..daee08d222 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyApplicationPartFactory.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 System.Collections.Generic; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// Configures an assembly as a . + /// + public class CompiledRazorAssemblyApplicationPartFactory : ApplicationPartFactory + { + /// + /// Gets the sequence of instances that are created by this instance of . + /// + /// Applications may use this method to get the same behavior as this factory produces during MVC's default part discovery. + /// + /// + /// The . + /// The sequence of instances. + public static IEnumerable GetDefaultApplicationParts(Assembly assembly) + { + if (assembly == null) + { + throw new ArgumentNullException(nameof(assembly)); + } + + yield return new CompiledRazorAssemblyPart(assembly); + } + + /// + public override IEnumerable GetApplicationParts(Assembly assembly) => GetDefaultApplicationParts(assembly); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyPart.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyPart.cs new file mode 100644 index 0000000000..36291ec4e8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyPart.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 System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Razor.Hosting; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// An for compiled Razor assemblies. + /// + public class CompiledRazorAssemblyPart : ApplicationPart, IRazorCompiledItemProvider + { + /// + /// Initializes a new instance of . + /// + /// The + public CompiledRazorAssemblyPart(Assembly assembly) + { + Assembly = assembly ?? throw new ArgumentNullException(nameof(assembly)); + } + + /// + /// Gets the . + /// + public Assembly Assembly { get; } + + /// + public override string Name => Assembly.GetName().Name; + + IEnumerable IRazorCompiledItemProvider.CompiledItems + { + get + { + var loader = new RazorCompiledItemLoader(); + return loader.LoadItems(Assembly); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/IRazorCompiledItemProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/IRazorCompiledItemProvider.cs new file mode 100644 index 0000000000..bdf11d6d59 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/IRazorCompiledItemProvider.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.Razor.Hosting; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + /// + /// Exposes one or more instances from an . + /// + public interface IRazorCompiledItemProvider + { + /// + /// Gets a sequence of instances. + /// + IEnumerable CompiledItems { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/RazorCompiledItemFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/RazorCompiledItemFeatureProvider.cs new file mode 100644 index 0000000000..d59c4e9ffb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/RazorCompiledItemFeatureProvider.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET 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.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Razor.Hosting; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + internal class RazorCompiledItemFeatureProvider : IApplicationFeatureProvider + { + public void PopulateFeature(IEnumerable parts, ViewsFeature feature) + { + foreach (var provider in parts.OfType()) + { + // 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 duplicates = provider.CompiledItems + .GroupBy(i => i.Identifier, StringComparer.OrdinalIgnoreCase) + .FirstOrDefault(g => g.Count() > 1); + + if (duplicates != null) + { + var viewsDiffereningInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.Identifier)); + + var message = string.Join( + Environment.NewLine, + Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase, + viewsDiffereningInCase); + throw new InvalidOperationException(message); + } + + foreach (var item in provider.CompiledItems) + { + var descriptor = new CompiledViewDescriptor(item, attribute: null); + feature.ViewDescriptors.Add(descriptor); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompilationFailedException.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompilationFailedException.cs new file mode 100644 index 0000000000..5e3899c0bb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompilationFailedException.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 System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + /// + /// An thrown when accessing the result of a failed compilation. + /// + public class CompilationFailedException : Exception, ICompilationException + { + /// + /// Instantiates a new instance of . + /// + /// s containing + /// details of the compilation failure. + public CompilationFailedException( + IEnumerable compilationFailures) + : base(FormatMessage(compilationFailures)) + { + if (compilationFailures == null) + { + throw new ArgumentNullException(nameof(compilationFailures)); + } + + CompilationFailures = compilationFailures; + } + + /// + public IEnumerable CompilationFailures { get; } + + private static string FormatMessage(IEnumerable compilationFailures) + { + return Resources.CompilationFailed + Environment.NewLine + + string.Join( + Environment.NewLine, + compilationFailures.SelectMany(f => f.Messages).Select(message => message.FormattedMessage)); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompiledViewDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompiledViewDescriptor.cs new file mode 100644 index 0000000000..4b4c1ec3e8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompiledViewDescriptor.cs @@ -0,0 +1,91 @@ +// Copyright (c) .NET 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.Razor.Internal; +using Microsoft.AspNetCore.Razor.Hosting; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + /// + /// Represents a compiled Razor View or Page. + /// + public class CompiledViewDescriptor + { + /// + /// Creates a new . + /// + public CompiledViewDescriptor() + { + + } + + /// + /// Creates a new . At least one of or + /// must be non-null. + /// + /// The . + /// The . + public CompiledViewDescriptor(RazorCompiledItem item, RazorViewAttribute attribute) + { + if (item == null && attribute == null) + { + // We require at least one of these to be specified. + throw new ArgumentException(Resources.FormatCompiledViewDescriptor_NoData(nameof(item), nameof(attribute))); + } + + Item = item; + + // + // For now we expect that MVC views and pages will still have either: + // [RazorView(...)] or + // [RazorPage(...)]. + // + // In theory we could look at the 'Item.Kind' to determine what kind of thing we're dealing + // with, but for compat reasons we're basing it on ViewAttribute since that's what 2.0 had. + ViewAttribute = attribute; + + // We don't have access to the file provider here so we can't check if the files + // even exist or what their checksums are. For now leave this empty, it will be updated + // later. + ExpirationTokens = Array.Empty(); + RelativePath = ViewPath.NormalizePath(item?.Identifier ?? attribute.Path); + IsPrecompiled = true; + } + + /// + /// The normalized application relative path of the view. + /// + public string RelativePath { get; set; } + + /// + /// Gets or sets the decorating the view. + /// + /// + /// May be null. + /// + public RazorViewAttribute ViewAttribute { get; set; } + + /// + /// instances that indicate when this result has expired. + /// + public IList ExpirationTokens { get; set; } + + /// + /// Gets a value that determines if the view is precompiled. + /// + public bool IsPrecompiled { get; set; } + + /// + /// Gets the descriptor for this view. + /// + public RazorCompiledItem Item { get; set; } + + /// + /// Gets the type of the compiled item. + /// + public Type Type => Item?.Type ?? ViewAttribute?.ViewType; + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompiler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompiler.cs new file mode 100644 index 0000000000..f861fa8c58 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompiler.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.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + public interface IViewCompiler + { + Task CompileAsync(string relativePath); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilerProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilerProvider.cs new file mode 100644 index 0000000000..34c65481f6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilerProvider.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 Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + public interface IViewCompilerProvider + { + IViewCompiler GetCompiler(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs new file mode 100644 index 0000000000..7ad99a2afe --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.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.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + /// + /// Specifies the list of used in Razor compilation. + /// + public class MetadataReferenceFeature + { + /// + /// Gets the instances. + /// + public IList MetadataReferences { get; } = new List(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs new file mode 100644 index 0000000000..f5a42ace01 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.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.IO; +using System.Linq; +using System.Reflection.PortableExecutable; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.DependencyModel; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + /// + /// An for that + /// uses for registered instances to create + /// . + /// + public class MetadataReferenceFeatureProvider : IApplicationFeatureProvider + { + /// + public void PopulateFeature(IEnumerable parts, MetadataReferenceFeature feature) + { + if (parts == null) + { + throw new ArgumentNullException(nameof(parts)); + } + + if (feature == null) + { + throw new ArgumentNullException(nameof(feature)); + } + + var libraryPaths = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var providerPart in parts.OfType()) + { + var referencePaths = providerPart.GetReferencePaths(); + foreach (var path in referencePaths) + { + if (libraryPaths.Add(path)) + { + var metadataReference = CreateMetadataReference(path); + feature.MetadataReferences.Add(metadataReference); + } + } + } + } + + private static MetadataReference CreateMetadataReference(string path) + { + using (var stream = File.OpenRead(path)) + { + var moduleMetadata = ModuleMetadata.CreateFromStream(stream, PEStreamOptions.PrefetchMetadata); + var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata); + + return assemblyMetadata.GetReference(filePath: path); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorReferenceManager.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorReferenceManager.cs new file mode 100644 index 0000000000..5b11ce10e3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorReferenceManager.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.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + /// + /// Manages compilation references for Razor compilation. + /// + public abstract class RazorReferenceManager + { + /// + /// Gets the set of compilation references to be used for Razor compilation. + /// + public abstract IReadOnlyList CompilationReferences { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewAttribute.cs new file mode 100644 index 0000000000..1ac6aba09a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewAttribute.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.Razor.Compilation +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class RazorViewAttribute : Attribute + { + public RazorViewAttribute(string path, Type viewType) + { + Path = path; + ViewType = viewType; + } + + /// + /// Gets the path of the view. + /// + public string Path { get; } + + /// + /// Gets the view type. + /// + public Type ViewType { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RoslynCompilationContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RoslynCompilationContext.cs new file mode 100644 index 0000000000..5536969721 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RoslynCompilationContext.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 System; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + /// + /// Context object used to pass information about the current Razor page compilation. + /// + public class RoslynCompilationContext + { + /// + /// Constructs a new instance of the type. + /// + /// to be set to property. + public RoslynCompilationContext(CSharpCompilation compilation) + { + if (compilation == null) + { + throw new ArgumentNullException(nameof(compilation)); + } + + Compilation = compilation; + } + + /// + /// Gets or sets the used for current source file compilation. + /// + public CSharpCompilation Compilation { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeature.cs new file mode 100644 index 0000000000..dde65a6340 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeature.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.Razor.Compilation +{ + public class ViewsFeature + { + public IList ViewDescriptors { get; } = new List(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs new file mode 100644 index 0000000000..deb57205de --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + /// + /// An for . + /// + [Obsolete("This type is obsolete and will be removed in a future version. See " + nameof(IRazorCompiledItemProvider) + " for alternatives.")] + public class ViewsFeatureProvider : IApplicationFeatureProvider + { + public static readonly string PrecompiledViewsAssemblySuffix = ".PrecompiledViews"; + + /// + public void PopulateFeature(IEnumerable parts, ViewsFeature feature) + { + foreach (var assemblyPart in parts.OfType()) + { + var viewAttributes = GetViewAttributes(assemblyPart) + .Select(attribute => (Attribute: attribute, RelativePath: ViewPath.NormalizePath(attribute.Path))); + + var duplicates = viewAttributes.GroupBy(a => a.RelativePath, StringComparer.OrdinalIgnoreCase) + .FirstOrDefault(g => g.Count() > 1); + + if (duplicates != null) + { + // 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 message = string.Join( + Environment.NewLine, + Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase, + viewsDiffereningInCase); + throw new InvalidOperationException(message); + } + + foreach (var (attribute, relativePath) in viewAttributes) + { + var viewDescriptor = new CompiledViewDescriptor + { + ExpirationTokens = Array.Empty(), + RelativePath = relativePath, + ViewAttribute = attribute, + IsPrecompiled = true, + }; + + feature.ViewDescriptors.Add(viewDescriptor); + } + } + } + + /// + /// Gets the sequence of instances associated with the specified . + /// + /// The . + /// The sequence of instances. + protected virtual IEnumerable GetViewAttributes(AssemblyPart assemblyPart) + { + if (assemblyPart == null) + { + throw new ArgumentNullException(nameof(assemblyPart)); + } + + var featureAssembly = GetFeatureAssembly(assemblyPart); + if (featureAssembly != null) + { + return featureAssembly.GetCustomAttributes(); + } + + return Enumerable.Empty(); + } + + private static Assembly GetFeatureAssembly(AssemblyPart assemblyPart) + { + if (assemblyPart.Assembly.IsDynamic || string.IsNullOrEmpty((string)assemblyPart.Assembly.Location)) + { + return null; + } + + var precompiledAssemblyFileName = assemblyPart.Assembly.GetName().Name + + PrecompiledViewsAssemblySuffix + + ".dll"; + + var precompiledAssemblyFilePath = Path.Combine( + Path.GetDirectoryName(assemblyPart.Assembly.Location), + precompiledAssemblyFileName); + + if (File.Exists(precompiledAssemblyFilePath)) + { + try + { + return Assembly.LoadFile(precompiledAssemblyFilePath); + } + catch (FileLoadException) + { + // Don't throw if assembly cannot be loaded. This can happen if the file is not a managed assembly. + } + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs new file mode 100644 index 0000000000..8b47c02345 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs @@ -0,0 +1,91 @@ +// Copyright (c) .NET 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.Razor; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extensions methods for configuring MVC via an . + /// + public static class MvcRazorMvcBuilderExtensions + { + /// + /// Configures a set of for the application. + /// + /// The . + /// An action to configure the . + /// The . + public static IMvcBuilder AddRazorOptions( + 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; + } + + /// + /// Registers tag helpers as services and replaces the existing + /// with an . + /// + /// The instance this method extends. + /// The instance this method extends. + public static IMvcBuilder AddTagHelpersAsServices(this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + TagHelpersAsServices.AddTagHelpersAsServices(builder.PartManager, builder.Services); + return builder; + } + + /// + /// Adds an initialization callback for a given . + /// + /// + /// The callback will be invoked on any instance before the + /// method is called. + /// + /// The type of being initialized. + /// The instance this method extends. + /// An action to initialize the . + /// The instance this method extends. + public static IMvcBuilder InitializeTagHelper( + this IMvcBuilder builder, + Action initialize) + where TTagHelper : ITagHelper + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (initialize == null) + { + throw new ArgumentNullException(nameof(initialize)); + } + + var initializer = new TagHelperInitializer(initialize); + + builder.Services.AddSingleton(typeof(ITagHelperInitializer), initializer); + + return builder; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs new file mode 100644 index 0000000000..2eb17bc862 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.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; +using System.Diagnostics; +using System.Linq; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Mvc; +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.Internal; +using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using CompilationTagHelperFeature = Microsoft.CodeAnalysis.Razor.CompilationTagHelperFeature; +using DefaultTagHelperDescriptorProvider = Microsoft.CodeAnalysis.Razor.DefaultTagHelperDescriptorProvider; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class MvcRazorMvcCoreBuilderExtensions + { + public static IMvcCoreBuilder AddRazorViewEngine(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddViews(); + AddRazorViewEngineFeatureProviders(builder); + AddRazorViewEngineServices(builder.Services); + return builder; + } + + public static IMvcCoreBuilder AddRazorViewEngine( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + builder.AddViews(); + + AddRazorViewEngineFeatureProviders(builder); + AddRazorViewEngineServices(builder.Services); + + builder.Services.Configure(setupAction); + + return builder; + } + + private static void AddRazorViewEngineFeatureProviders(IMvcCoreBuilder builder) + { + if (!builder.PartManager.FeatureProviders.OfType().Any()) + { + builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); + } + + if (!builder.PartManager.FeatureProviders.OfType().Any()) + { + builder.PartManager.FeatureProviders.Add(new TagHelperFeatureProvider()); + } + + // 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. + if (!builder.PartManager.FeatureProviders.OfType().Any()) + { + builder.PartManager.FeatureProviders.Add(new RazorCompiledItemFeatureProvider()); + } + +#pragma warning disable CS0618 // Type or member is obsolete + if (!builder.PartManager.FeatureProviders.OfType().Any()) + { + builder.PartManager.FeatureProviders.Add(new ViewsFeatureProvider()); + } +#pragma warning restore CS0618 // Type or member is obsolete + } + + /// + /// Registers discovered tag helpers as services and changes the existing + /// for an . + /// + /// The instance this method extends. + /// The instance this method extends. + public static IMvcCoreBuilder AddTagHelpersAsServices(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + TagHelpersAsServices.AddTagHelpersAsServices(builder.PartManager, builder.Services); + return builder; + } + + /// + /// Adds an initialization callback for a given . + /// + /// + /// The callback will be invoked on any instance before the + /// method is called. + /// + /// The type of being initialized. + /// The instance this method extends. + /// An action to initialize the . + /// The instance this method extends. + public static IMvcCoreBuilder InitializeTagHelper( + this IMvcCoreBuilder builder, + Action initialize) + where TTagHelper : ITagHelper + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (initialize == null) + { + throw new ArgumentNullException(nameof(initialize)); + } + + var initializer = new TagHelperInitializer(initialize); + + builder.Services.AddSingleton(typeof(ITagHelperInitializer), initializer); + + return builder; + } + + // Internal for testing. + internal static void AddRazorViewEngineServices(IServiceCollection services) + { + services.TryAddSingleton(); + services.TryAddSingleton(); + + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcRazorMvcViewOptionsSetup>()); + + services.TryAddEnumerable( + ServiceDescriptor.Transient, RazorViewEngineOptionsSetup>()); + + services.TryAddSingleton< + IRazorViewEngineFileProviderAccessor, + DefaultRazorViewEngineFileProviderAccessor>(); + + services.TryAddSingleton(s => + { + var pageFactory = s.GetRequiredService(); + var pageActivator = s.GetRequiredService(); + var htmlEncoder = s.GetRequiredService(); + var optionsAccessor = s.GetRequiredService>(); + var razorFileSystem = s.GetRequiredService(); + var loggerFactory = s.GetRequiredService(); + var diagnosticSource = s.GetRequiredService(); + + var viewEngine = new RazorViewEngine(pageFactory, pageActivator, htmlEncoder, optionsAccessor, razorFileSystem, loggerFactory, diagnosticSource); + return viewEngine; + }); + services.TryAddSingleton(); + + // In the default scenario the following services are singleton by virtue of being initialized as part of + // creating the singleton RazorViewEngine instance. + services.TryAddTransient(); + + // + // Razor compilation infrastructure + // + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(s => + { + var fileSystem = s.GetRequiredService(); + var projectEngine = RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem, builder => + { + RazorExtensions.Register(builder); + + // Roslyn + TagHelpers infrastructure + var metadataReferenceFeature = s.GetRequiredService(); + builder.Features.Add(metadataReferenceFeature); + builder.Features.Add(new CompilationTagHelperFeature()); + + // TagHelperDescriptorProviders (actually do tag helper discovery) + builder.Features.Add(new DefaultTagHelperDescriptorProvider()); + builder.Features.Add(new ViewComponentTagHelperDescriptorProvider()); + }); + + return projectEngine; + }); + + // Legacy Razor compilation services + services.TryAddSingleton(s => s.GetRequiredService().FileSystem); + services.TryAddSingleton(); + services.TryAddSingleton(s => s.GetRequiredService().Engine); + + // This caches Razor page activation details that are valid for the lifetime of the application. + services.TryAddSingleton(); + + // Only want one ITagHelperActivator and ITagHelperComponentPropertyActivator so it can cache Type activation information. Types won't conflict. + services.TryAddSingleton(); + services.TryAddSingleton(); + + services.TryAddSingleton(); + + // TagHelperComponents manager + services.TryAddScoped(); + + // Consumed by the Cache tag helper to cache results across the lifetime of the application. + services.TryAddSingleton(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/HelperResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/HelperResult.cs new file mode 100644 index 0000000000..d69f37d59e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/HelperResult.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.IO; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Represents a deferred write operation in a . + /// + public class HelperResult : IHtmlContent + { + private readonly Func _asyncAction; + + /// + /// Creates a new instance of . + /// + /// The asynchronous delegate to invoke when + /// is called. + /// Calls to result in a blocking invocation of + /// . + public HelperResult(Func asyncAction) + { + if (asyncAction == null) + { + throw new ArgumentNullException(nameof(asyncAction)); + } + + _asyncAction = asyncAction; + } + + /// + /// Gets the asynchronous delegate to invoke when is called. + /// + public Func WriteAction => _asyncAction; + + /// + /// Method invoked to produce content from the . + /// + /// The instance to write to. + /// The to encode the content. + public virtual void WriteTo(TextWriter writer, HtmlEncoder encoder) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + if (encoder == null) + { + throw new ArgumentNullException(nameof(encoder)); + } + + _asyncAction(writer).GetAwaiter().GetResult(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPage.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPage.cs new file mode 100644 index 0000000000..6a7773eae3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPage.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.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Represents properties and methods that are used by for execution. + /// + public interface IRazorPage + { + /// + /// Gets or sets the view context of the rendering view. + /// + ViewContext ViewContext { get; set; } + + /// + /// Gets or sets the body content. + /// + IHtmlContent BodyContent { get; set; } + + /// + /// Gets or sets a flag that determines if the layout of this page is being rendered. + /// + /// + /// Sections defined in a page are deferred and executed as part of the layout page. + /// When this flag is set, all write operations performed by the page are part of a + /// section being rendered. + /// + bool IsLayoutBeingRendered { get; set; } + + /// + /// Gets the application base relative path to the page. + /// + string Path { get; set; } + + /// + /// Gets or sets the path of a layout page. + /// + string Layout { get; set; } + + /// + /// Gets or sets the sections that can be rendered by this page. + /// + IDictionary PreviousSectionWriters { get; set; } + + /// + /// Gets the sections that are defined by this page. + /// + IDictionary SectionWriters { get; } + + /// + /// Renders the page and writes the output to the . + /// + /// A task representing the result of executing the page. + Task ExecuteAsync(); + + /// + /// Verifies that all sections defined in were rendered, or + /// the body was rendered if no sections were defined. + /// + /// if one or more sections were not rendered or if no sections were + /// defined and the body was not rendered. + void EnsureRenderedBodyOrSections(); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageActivator.cs new file mode 100644 index 0000000000..4a26dafcd7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageActivator.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.AspNetCore.Mvc.Rendering; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Provides methods to activate properties on a instance. + /// + public interface IRazorPageActivator + { + /// + /// When implemented in a type, activates an instantiated page. + /// + /// The page to activate. + /// The for the executing view. + void Activate(IRazorPage page, ViewContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageFactoryProvider.cs new file mode 100644 index 0000000000..a3989e07be --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageFactoryProvider.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.Razor +{ + /// + /// Defines methods that are used for creating instances at a given path. + /// + public interface IRazorPageFactoryProvider + { + /// + /// Creates a factory for the specified path. + /// + /// The path to locate the page. + /// The instance. + RazorPageFactoryResult CreateFactory(string relativePath); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorViewEngine.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorViewEngine.cs new file mode 100644 index 0000000000..6af2cafef8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorViewEngine.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 Microsoft.AspNetCore.Mvc.ViewEngines; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// An used to render pages that use the Razor syntax. + /// + public interface IRazorViewEngine : IViewEngine + { + /// + /// Finds the page with the given using view locations and information from the + /// . + /// + /// The . + /// The name or path of the page. + /// The of locating the page. + /// + /// Use when the absolute or relative + /// path of the page is known. + /// . + /// + RazorPageResult FindPage(ActionContext context, string pageName); + + /// + /// Gets the page with the given , relative to + /// unless is already absolute. + /// + /// The absolute path to the currently-executing page, if any. + /// The path to the page. + /// The of locating the page. + /// . + RazorPageResult GetPage(string executingFilePath, string pagePath); + + /// + /// Converts the given to be absolute, relative to + /// unless is already absolute. + /// + /// The absolute path to the currently-executing page, if any. + /// The path to the page. + /// + /// The combination of and if + /// is a relative path. The value (unchanged) + /// otherwise. + /// + string GetAbsolutePath(string executingFilePath, string pagePath); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperActivator.cs new file mode 100644 index 0000000000..7757980390 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperActivator.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 Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Provides methods to create a tag helper. + /// + public interface ITagHelperActivator + { + /// + /// Creates an . + /// + /// The type. + /// The for the executing view. + /// The tag helper. + TTagHelper Create(ViewContext context) where TTagHelper : ITagHelper; + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperFactory.cs new file mode 100644 index 0000000000..181b805655 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperFactory.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.Mvc.Rendering; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Provides methods to create and initialize tag helpers. + /// + public interface ITagHelperFactory + { + /// + /// Creates a new tag helper for the specified . + /// + /// for the executing view. + /// The tag helper. + TTagHelper CreateTagHelper(ViewContext context) where TTagHelper : ITagHelper; + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperInitializerOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperInitializerOfT.cs new file mode 100644 index 0000000000..a3379fc2a0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperInitializerOfT.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.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Initializes an before it's executed. + /// + /// The type. + public interface ITagHelperInitializer + where TTagHelper : ITagHelper + { + /// + /// Initializes the . + /// + /// The to initialize. + /// The for the executing view. + void Initialize(TTagHelper helper, ViewContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IViewLocationExpander.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IViewLocationExpander.cs new file mode 100644 index 0000000000..d5ed2f7c7d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IViewLocationExpander.cs @@ -0,0 +1,45 @@ +// Copyright (c) .NET 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.Razor +{ + /// + /// Specifies the contracts for a view location expander that is used by instances to + /// determine search paths for a view. + /// + /// + /// Individual s are invoked in two steps: + /// (1) is invoked and each expander + /// adds values that it would later consume as part of + /// . + /// The populated values are used to determine a cache key - if all values are identical to the last time + /// was invoked, the cached result + /// is used as the view location. + /// (2) If no result was found in the cache or if a view was not found at the cached location, + /// is invoked to determine + /// all potential paths for a view. + /// + public interface IViewLocationExpander + { + /// + /// Invoked by a to determine the values that would be consumed by this instance + /// of . The calculated values are used to determine if the view location + /// has changed since the last time it was located. + /// + /// The for the current view location + /// expansion operation. + void PopulateValues(ViewLocationExpanderContext context); + + /// + /// Invoked by a to determine potential locations for a view. + /// + /// The for the current view location + /// expansion operation. + /// The sequence of view locations to expand. + /// A list of expanded view locations. + IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, + IEnumerable viewLocations); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs new file mode 100644 index 0000000000..93703d49c9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs @@ -0,0 +1,231 @@ +// Copyright (c) .NET 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.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.DependencyModel; +using DependencyContextCompilationOptions = Microsoft.Extensions.DependencyModel.CompilationOptions; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class CSharpCompiler + { + private readonly RazorReferenceManager _referenceManager; + private readonly IHostingEnvironment _hostingEnvironment; + private bool _optionsInitialized; + private CSharpParseOptions _parseOptions; + private CSharpCompilationOptions _compilationOptions; + private EmitOptions _emitOptions; + private bool _emitPdb; + + public CSharpCompiler(RazorReferenceManager manager, IHostingEnvironment hostingEnvironment) + { + _referenceManager = manager ?? throw new ArgumentNullException(nameof(manager)); + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + public virtual CSharpParseOptions ParseOptions + { + get + { + EnsureOptions(); + return _parseOptions; + } + } + + public virtual CSharpCompilationOptions CSharpCompilationOptions + { + get + { + EnsureOptions(); + return _compilationOptions; + } + } + + public virtual bool EmitPdb + { + get + { + EnsureOptions(); + return _emitPdb; + } + } + + public virtual EmitOptions EmitOptions + { + get + { + EnsureOptions(); + return _emitOptions; + } + } + + public SyntaxTree CreateSyntaxTree(SourceText sourceText) + { + return CSharpSyntaxTree.ParseText( + sourceText, + options: ParseOptions); + } + + public CSharpCompilation CreateCompilation(string assemblyName) + { + return CSharpCompilation.Create( + assemblyName, + options: CSharpCompilationOptions, + references: _referenceManager.CompilationReferences); + } + + // Internal for unit testing. + protected internal virtual DependencyContextCompilationOptions GetDependencyContextCompilationOptions() + { + if (!string.IsNullOrEmpty(_hostingEnvironment.ApplicationName)) + { + var applicationAssembly = Assembly.Load(new AssemblyName(_hostingEnvironment.ApplicationName)); + var dependencyContext = DependencyContext.Load(applicationAssembly); + if (dependencyContext?.CompilationOptions != null) + { + return dependencyContext.CompilationOptions; + } + } + + return DependencyContextCompilationOptions.Default; + } + + private void EnsureOptions() + { + if (!_optionsInitialized) + { + var dependencyContextOptions = GetDependencyContextCompilationOptions(); + _parseOptions = GetParseOptions(_hostingEnvironment, dependencyContextOptions); + _compilationOptions = GetCompilationOptions(_hostingEnvironment, dependencyContextOptions); + _emitOptions = GetEmitOptions(dependencyContextOptions); + + _optionsInitialized = true; + } + } + + private EmitOptions GetEmitOptions(DependencyContextCompilationOptions dependencyContextOptions) + { + // Assume we're always producing pdbs unless DebugType = none + _emitPdb = true; + DebugInformationFormat debugInformationFormat; + if (string.IsNullOrEmpty(dependencyContextOptions.DebugType)) + { + debugInformationFormat = SymbolsUtility.SupportsFullPdbGeneration() ? + DebugInformationFormat.Pdb : + DebugInformationFormat.PortablePdb; + } + else + { + // Based on https://github.com/dotnet/roslyn/blob/1d28ff9ba248b332de3c84d23194a1d7bde07e4d/src/Compilers/CSharp/Portable/CommandLine/CSharpCommandLineParser.cs#L624-L640 + switch (dependencyContextOptions.DebugType.ToLower()) + { + case "none": + // There isn't a way to represent none in DebugInformationFormat. + // We'll set EmitPdb to false and let callers handle it by setting a null pdb-stream. + _emitPdb = false; + return new EmitOptions(); + case "portable": + debugInformationFormat = DebugInformationFormat.PortablePdb; + break; + case "embedded": + // Roslyn does not expose enough public APIs to produce a binary with embedded pdbs. + // We'll produce PortablePdb instead to continue providing a reasonable user experience. + debugInformationFormat = DebugInformationFormat.PortablePdb; + break; + case "full": + case "pdbonly": + debugInformationFormat = SymbolsUtility.SupportsFullPdbGeneration() ? + DebugInformationFormat.Pdb : + DebugInformationFormat.PortablePdb; + break; + default: + throw new InvalidOperationException(Resources.FormatUnsupportedDebugInformationFormat(dependencyContextOptions.DebugType)); + } + } + + var emitOptions = new EmitOptions(debugInformationFormat: debugInformationFormat); + return emitOptions; + } + + private static CSharpCompilationOptions GetCompilationOptions( + IHostingEnvironment hostingEnvironment, + DependencyContextCompilationOptions dependencyContextOptions) + { + var csharpCompilationOptions = new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary); + + // Disable 1702 until roslyn turns this off by default + csharpCompilationOptions = csharpCompilationOptions.WithSpecificDiagnosticOptions( + new Dictionary + { + {"CS1701", ReportDiagnostic.Suppress}, // Binding redirects + {"CS1702", ReportDiagnostic.Suppress}, + {"CS1705", ReportDiagnostic.Suppress} + }); + + if (dependencyContextOptions.AllowUnsafe.HasValue) + { + csharpCompilationOptions = csharpCompilationOptions.WithAllowUnsafe( + dependencyContextOptions.AllowUnsafe.Value); + } + + OptimizationLevel optimizationLevel; + if (dependencyContextOptions.Optimize.HasValue) + { + optimizationLevel = dependencyContextOptions.Optimize.Value ? + OptimizationLevel.Release : + OptimizationLevel.Debug; + } + else + { + optimizationLevel = hostingEnvironment.IsDevelopment() ? + OptimizationLevel.Debug : + OptimizationLevel.Release; + } + csharpCompilationOptions = csharpCompilationOptions.WithOptimizationLevel(optimizationLevel); + + if (dependencyContextOptions.WarningsAsErrors.HasValue) + { + var reportDiagnostic = dependencyContextOptions.WarningsAsErrors.Value ? + ReportDiagnostic.Error : + ReportDiagnostic.Default; + csharpCompilationOptions = csharpCompilationOptions.WithGeneralDiagnosticOption(reportDiagnostic); + } + + return csharpCompilationOptions; + } + + private static CSharpParseOptions GetParseOptions( + IHostingEnvironment hostingEnvironment, + DependencyContextCompilationOptions dependencyContextOptions) + { + var configurationSymbol = hostingEnvironment.IsDevelopment() ? "DEBUG" : "RELEASE"; + var defines = dependencyContextOptions.Defines.Concat(new[] { configurationSymbol }); + + var parseOptions = new CSharpParseOptions(preprocessorSymbols: defines); + + if (!string.IsNullOrEmpty(dependencyContextOptions.LanguageVersion)) + { + if (LanguageVersionFacts.TryParse(dependencyContextOptions.LanguageVersion, out var languageVersion)) + { + parseOptions = parseOptions.WithLanguageVersion(languageVersion); + } + else + { + Debug.Fail($"LanguageVersion {languageVersion} specified in the deps file could not be parsed."); + } + } + + return parseOptions; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ChecksumValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ChecksumValidator.cs new file mode 100644 index 0000000000..fea6a7ba2d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ChecksumValidator.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.Razor.Hosting; +using Microsoft.AspNetCore.Razor.Language; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public static class ChecksumValidator + { + public static bool IsRecompilationSupported(RazorCompiledItem item) + { + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + // A Razor item only supports recompilation if its primary source file has a checksum. + // + // Other files (view imports) may or may not have existed at the time of compilation, + // so we may not have checksums for them. + var checksums = item.GetChecksumMetadata(); + return checksums.Any(c => string.Equals(item.Identifier, c.Identifier, StringComparison.OrdinalIgnoreCase)); + } + + // Validates that we can use an existing precompiled view by comparing checksums with files on + // disk. + public static bool IsItemValid(RazorProjectFileSystem fileSystem, RazorCompiledItem item) + { + if (fileSystem == null) + { + throw new ArgumentNullException(nameof(fileSystem)); + } + + if (item == null) + { + throw new ArgumentNullException(nameof(item)); + } + + var checksums = item.GetChecksumMetadata(); + + // The checksum that matches 'Item.Identity' in this list is significant. That represents the main file. + // + // We don't really care about the validation unless the main file exists. This is because we expect + // most sites to have some _ViewImports in common location. That means that in the case you're + // using views from a 3rd party library, you'll always have **some** conflicts. + // + // The presence of the main file with the same content is a very strong signal that you're in a + // development scenario. + var primaryChecksum = checksums + .FirstOrDefault(c => string.Equals(item.Identifier, c.Identifier, StringComparison.OrdinalIgnoreCase)); + if (primaryChecksum == null) + { + // No primary checksum, assume valid. + return true; + } + + var projectItem = fileSystem.GetItem(primaryChecksum.Identifier); + if (!projectItem.Exists) + { + // Main file doesn't exist - assume valid. + return true; + } + + var sourceDocument = RazorSourceDocument.ReadFrom(projectItem); + if (!string.Equals(sourceDocument.GetChecksumAlgorithm(), primaryChecksum.ChecksumAlgorithm) || + !ChecksumsEqual(primaryChecksum.Checksum, sourceDocument.GetChecksum())) + { + // Main file exists, but checksums not equal. + return false; + } + + for (var i = 0; i < checksums.Count; i++) + { + var checksum = checksums[i]; + if (string.Equals(item.Identifier, checksum.Identifier, StringComparison.OrdinalIgnoreCase)) + { + // Ignore primary checksum on this pass. + continue; + } + + var importItem = fileSystem.GetItem(checksum.Identifier); + if (!importItem.Exists) + { + // Import file doesn't exist - assume invalid. + return false; + } + + sourceDocument = RazorSourceDocument.ReadFrom(importItem); + if (!string.Equals(sourceDocument.GetChecksumAlgorithm(), checksum.ChecksumAlgorithm) || + !ChecksumsEqual(checksum.Checksum, sourceDocument.GetChecksum())) + { + // Import file exists, but checksums not equal. + return false; + } + } + + return true; + } + + private static bool ChecksumsEqual(string checksum, byte[] bytes) + { + if (bytes.Length * 2 != checksum.Length) + { + return false; + } + + for (var i = 0; i < bytes.Length; i++) + { + var text = bytes[i].ToString("x2"); + if (checksum[i * 2] != text[0] || checksum[i * 2 + 1] != text[1]) + { + return false; + } + } + + return true; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilationFailedExceptionFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilationFailedExceptionFactory.cs new file mode 100644 index 0000000000..0be6fa6b7d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilationFailedExceptionFactory.cs @@ -0,0 +1,159 @@ +// Copyright (c) .NET 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.Diagnostics; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + internal static class CompilationFailedExceptionFactory + { + // error CS0234: The type or namespace name 'C' does not exist in the namespace 'N' (are you missing + // an assembly reference?) + private const string CS0234 = nameof(CS0234); + // error CS0246: The type or namespace name 'T' could not be found (are you missing a using directive + // or an assembly reference?) + private const string CS0246 = nameof(CS0246); + + public static CompilationFailedException Create( + RazorCodeDocument codeDocument, + IEnumerable diagnostics) + { + // If a SourceLocation does not specify a file path, assume it is produced from parsing the current file. + var messageGroups = diagnostics.GroupBy( + razorError => razorError.Span.FilePath ?? codeDocument.Source.FilePath, + StringComparer.Ordinal); + + var failures = new List(); + foreach (var group in messageGroups) + { + var filePath = group.Key; + var fileContent = ReadContent(codeDocument, filePath); + var compilationFailure = new CompilationFailure( + filePath, + fileContent, + compiledContent: string.Empty, + messages: group.Select(parserError => CreateDiagnosticMessage(parserError, filePath))); + failures.Add(compilationFailure); + } + + return new CompilationFailedException(failures); + } + + public static CompilationFailedException Create( + RazorCodeDocument codeDocument, + string compilationContent, + string assemblyName, + IEnumerable diagnostics) + { + var diagnosticGroups = diagnostics + .Where(diagnostic => diagnostic.IsWarningAsError || diagnostic.Severity == DiagnosticSeverity.Error) + .GroupBy(diagnostic => GetFilePath(codeDocument, diagnostic), StringComparer.Ordinal); + + var failures = new List(); + foreach (var group in diagnosticGroups) + { + var sourceFilePath = group.Key; + string sourceFileContent; + if (string.Equals(assemblyName, sourceFilePath, StringComparison.Ordinal)) + { + // The error is in the generated code and does not have a mapping line pragma + sourceFileContent = compilationContent; + sourceFilePath = Resources.GeneratedCodeFileName; + } + else + { + sourceFileContent = ReadContent(codeDocument, sourceFilePath); + } + + string additionalMessage = null; + if (group.Any(g => + string.Equals(CS0234, g.Id, StringComparison.OrdinalIgnoreCase) || + string.Equals(CS0246, g.Id, StringComparison.OrdinalIgnoreCase))) + { + additionalMessage = Resources.FormatCompilation_DependencyContextIsNotSpecified( + "Microsoft.NET.Sdk.Web", + "PreserveCompilationContext"); + } + + var compilationFailure = new CompilationFailure( + sourceFilePath, + sourceFileContent, + compilationContent, + group.Select(GetDiagnosticMessage), + additionalMessage); + + failures.Add(compilationFailure); + } + + return new CompilationFailedException(failures); + } + + private static string ReadContent(RazorCodeDocument codeDocument, string filePath) + { + RazorSourceDocument sourceDocument; + if (string.IsNullOrEmpty(filePath) || string.Equals(codeDocument.Source.FilePath, filePath, StringComparison.Ordinal)) + { + sourceDocument = codeDocument.Source; + } + else + { + sourceDocument = codeDocument.Imports.FirstOrDefault(f => string.Equals(f.FilePath, filePath, StringComparison.Ordinal)); + } + + if (sourceDocument != null) + { + var contentChars = new char[sourceDocument.Length]; + sourceDocument.CopyTo(0, contentChars, 0, sourceDocument.Length); + return new string(contentChars); + } + + return string.Empty; + } + + private static DiagnosticMessage GetDiagnosticMessage(Diagnostic diagnostic) + { + var mappedLineSpan = diagnostic.Location.GetMappedLineSpan(); + return new DiagnosticMessage( + diagnostic.GetMessage(), + CSharpDiagnosticFormatter.Instance.Format(diagnostic), + mappedLineSpan.Path, + mappedLineSpan.StartLinePosition.Line + 1, + mappedLineSpan.StartLinePosition.Character + 1, + mappedLineSpan.EndLinePosition.Line + 1, + mappedLineSpan.EndLinePosition.Character + 1); + } + + private static DiagnosticMessage CreateDiagnosticMessage( + RazorDiagnostic razorDiagnostic, + string filePath) + { + var sourceSpan = razorDiagnostic.Span; + var message = razorDiagnostic.GetMessage(); + return new DiagnosticMessage( + message: message, + formattedMessage: razorDiagnostic.ToString(), + filePath: filePath, + startLine: sourceSpan.LineIndex + 1, + startColumn: sourceSpan.CharacterIndex, + endLine: sourceSpan.LineIndex + 1, + endColumn: sourceSpan.CharacterIndex + sourceSpan.Length); + } + + private static string GetFilePath(RazorCodeDocument codeDocument, Diagnostic diagnostic) + { + if (diagnostic.Location == Location.None) + { + return codeDocument.Source.FilePath; + } + + return diagnostic.Location.GetMappedLineSpan().Path; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorPageFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorPageFactoryProvider.cs new file mode 100644 index 0000000000..b5c7341830 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorPageFactoryProvider.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.Linq.Expressions; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// Represents a that creates instances + /// from razor files in the file system. + /// + public class DefaultRazorPageFactoryProvider : IRazorPageFactoryProvider + { + private readonly IViewCompilerProvider _viewCompilerProvider; + + /// + /// Initializes a new instance of . + /// + /// The . + public DefaultRazorPageFactoryProvider(IViewCompilerProvider viewCompilerProvider) + { + _viewCompilerProvider = viewCompilerProvider; + } + + private IViewCompiler Compiler => _viewCompilerProvider.GetCompiler(); + + /// + public RazorPageFactoryResult CreateFactory(string relativePath) + { + if (relativePath == null) + { + throw new ArgumentNullException(nameof(relativePath)); + } + + if (relativePath.StartsWith("~/", StringComparison.Ordinal)) + { + // For tilde slash paths, drop the leading ~ to make it work with the underlying IFileProvider. + relativePath = relativePath.Substring(1); + } + + var compileTask = Compiler.CompileAsync(relativePath); + var viewDescriptor = compileTask.GetAwaiter().GetResult(); + + var viewType = viewDescriptor.Type; + if (viewType != null) + { + var newExpression = Expression.New(viewType); + var pathProperty = viewType.GetTypeInfo().GetProperty(nameof(IRazorPage.Path)); + + // Generate: page.Path = relativePath; + // Use the normalized path specified from the result. + var propertyBindExpression = Expression.Bind(pathProperty, Expression.Constant(viewDescriptor.RelativePath)); + var objectInitializeExpression = Expression.MemberInit(newExpression, propertyBindExpression); + var pageFactory = Expression + .Lambda>(objectInitializeExpression) + .Compile(); + return new RazorPageFactoryResult(viewDescriptor, pageFactory); + } + else + { + return new RazorPageFactoryResult(viewDescriptor, razorPageFactory: null); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorReferenceManager.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorReferenceManager.cs new file mode 100644 index 0000000000..f1d59ef770 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorReferenceManager.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET 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.Threading; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class DefaultRazorReferenceManager : RazorReferenceManager + { + private readonly ApplicationPartManager _partManager; + private readonly IList _additionalMetadataReferences; + private object _compilationReferencesLock = new object(); + private bool _compilationReferencesInitialized; + private IReadOnlyList _compilationReferences; + + public DefaultRazorReferenceManager( + ApplicationPartManager partManager, + IOptions optionsAccessor) + { + _partManager = partManager; + _additionalMetadataReferences = optionsAccessor.Value.AdditionalCompilationReferences; + } + + public override IReadOnlyList CompilationReferences + { + get + { + return LazyInitializer.EnsureInitialized( + ref _compilationReferences, + ref _compilationReferencesInitialized, + ref _compilationReferencesLock, + GetCompilationReferences); + } + } + + private IReadOnlyList GetCompilationReferences() + { + var feature = new MetadataReferenceFeature(); + _partManager.PopulateFeature(feature); + var applicationReferences = feature.MetadataReferences; + + if (_additionalMetadataReferences.Count == 0) + { + return applicationReferences.ToArray(); + } + + var compilationReferences = new List(applicationReferences.Count + _additionalMetadataReferences.Count); + compilationReferences.AddRange(applicationReferences); + compilationReferences.AddRange(_additionalMetadataReferences); + + return compilationReferences; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorViewEngineFileProviderAccessor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorViewEngineFileProviderAccessor.cs new file mode 100644 index 0000000000..5823a4b39c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorViewEngineFileProviderAccessor.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 Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// Default implementation of . + /// + public class DefaultRazorViewEngineFileProviderAccessor : IRazorViewEngineFileProviderAccessor + { + /// + /// Initializes a new instance of . + /// + /// Accessor to . + public DefaultRazorViewEngineFileProviderAccessor(IOptions optionsAccessor) + { + var fileProviders = optionsAccessor.Value.FileProviders; + if (fileProviders.Count == 0) + { + FileProvider = new NullFileProvider(); + } + else if (fileProviders.Count == 1) + { + FileProvider = fileProviders[0]; + } + else + { + FileProvider = new CompositeFileProvider(fileProviders); + } + } + + /// + /// Gets the used to look up Razor files. + /// + public IFileProvider FileProvider { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperActivator.cs new file mode 100644 index 0000000000..c52a77a2eb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperActivator.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; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// Default implementation of . + /// + public class DefaultTagHelperActivator : ITagHelperActivator + { + private readonly ITypeActivatorCache _typeActivatorCache; + + /// + /// Instantiates a new instance. + /// + /// The . + public DefaultTagHelperActivator(ITypeActivatorCache typeActivatorCache) + { + if (typeActivatorCache == null) + { + throw new ArgumentNullException(nameof(typeActivatorCache)); + } + + _typeActivatorCache = typeActivatorCache; + } + + /// + public TTagHelper Create(ViewContext context) + where TTagHelper : ITagHelper + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + return _typeActivatorCache.CreateInstance( + context.HttpContext.RequestServices, + typeof(TTagHelper)); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperFactory.cs new file mode 100644 index 0000000000..6dd51965c3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperFactory.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; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// Default implementation for . + /// + public class DefaultTagHelperFactory : ITagHelperFactory + { + private readonly ITagHelperActivator _activator; + private readonly ConcurrentDictionary[]> _injectActions; + private readonly Func[]> _getPropertiesToActivate; + private static readonly Func> _createActivateInfo = CreateActivateInfo; + + /// + /// Initializes a new instance. + /// + /// + /// The used to create tag helper instances. + /// + public DefaultTagHelperFactory(ITagHelperActivator activator) + { + if (activator == null) + { + throw new ArgumentNullException(nameof(activator)); + } + + _activator = activator; + _injectActions = new ConcurrentDictionary[]>(); + _getPropertiesToActivate = type => + PropertyActivator.GetPropertiesToActivate( + type, + typeof(ViewContextAttribute), + _createActivateInfo); + } + + /// + public TTagHelper CreateTagHelper(ViewContext context) + where TTagHelper : ITagHelper + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var tagHelper = _activator.Create(context); + + var propertiesToActivate = _injectActions.GetOrAdd( + tagHelper.GetType(), + _getPropertiesToActivate); + + for (var i = 0; i < propertiesToActivate.Length; i++) + { + var activateInfo = propertiesToActivate[i]; + activateInfo.Activate(tagHelper, context); + } + + InitializeTagHelper(tagHelper, context); + + return tagHelper; + } + + private static void InitializeTagHelper(TTagHelper tagHelper, ViewContext context) + where TTagHelper : ITagHelper + { + // Run any tag helper initializers in the container + var serviceProvider = context.HttpContext.RequestServices; + var initializers = serviceProvider.GetService>>(); + + foreach (var initializer in initializers) + { + initializer.Initialize(tagHelper, context); + } + } + + private static PropertyActivator CreateActivateInfo(PropertyInfo property) + { + return new PropertyActivator(property, viewContext => viewContext); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ExpressionRewriter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ExpressionRewriter.cs new file mode 100644 index 0000000000..6d3d9cc757 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ExpressionRewriter.cs @@ -0,0 +1,215 @@ +// Copyright (c) .NET 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.Linq.Expressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// An expression rewriter which can hoist a simple expression lambda into a private field. + /// + public class ExpressionRewriter : CSharpSyntaxRewriter + { + private static readonly string FieldNameTemplate = "__h{0}"; + + public ExpressionRewriter(SemanticModel semanticModel) + { + SemanticModel = semanticModel; + + Expressions = new List>(); + } + + // We only want to rewrite expressions for the top-level class definition. + private bool IsInsideClass { get; set; } + + private SemanticModel SemanticModel { get; } + + private List> Expressions { get; } + + public static CSharpCompilation Rewrite(CSharpCompilation compilation) + { + var rewrittenTrees = new List(); + foreach (var tree in compilation.SyntaxTrees) + { + var semanticModel = compilation.GetSemanticModel(tree, ignoreAccessibility: true); + var rewriter = new ExpressionRewriter(semanticModel); + + var rewrittenTree = tree.WithRootAndOptions(rewriter.Visit(tree.GetRoot()), tree.Options); + rewrittenTrees.Add(rewrittenTree); + } + + return compilation.RemoveAllSyntaxTrees().AddSyntaxTrees(rewrittenTrees); + } + + public override SyntaxNode VisitClassDeclaration(ClassDeclarationSyntax node) + { + if (IsInsideClass) + { + // Avoid recursing into nested classes. + return node; + } + + Expressions.Clear(); + + IsInsideClass = true; + + // Call base first to visit all the children and populate Expressions. + var classDeclaration = (ClassDeclarationSyntax)base.VisitClassDeclaration(node); + + IsInsideClass = false; + + var memberDeclarations = new List(); + foreach (var kvp in Expressions) + { + var expression = kvp.Key; + var memberName = kvp.Value.GetFirstToken(); + + var expressionType = SemanticModel.GetTypeInfo(expression).ConvertedType; + var declaration = SyntaxFactory.FieldDeclaration( + SyntaxFactory.List(), + SyntaxFactory.TokenList( + SyntaxFactory.Token(SyntaxKind.PrivateKeyword), + SyntaxFactory.Token(SyntaxKind.StaticKeyword), + SyntaxFactory.Token(SyntaxKind.ReadOnlyKeyword)), + SyntaxFactory.VariableDeclaration( + SyntaxFactory.ParseTypeName(expressionType.ToDisplayString( + SymbolDisplayFormat.FullyQualifiedFormat)), + SyntaxFactory.SingletonSeparatedList( + SyntaxFactory.VariableDeclarator( + memberName, + SyntaxFactory.BracketedArgumentList(), + SyntaxFactory.EqualsValueClause(expression))))) + .WithTriviaFrom(expression); + memberDeclarations.Add(declaration); + } + + return classDeclaration.AddMembers(memberDeclarations.ToArray()); + } + + public override SyntaxNode VisitSimpleLambdaExpression(SimpleLambdaExpressionSyntax node) + { + Debug.Assert(IsInsideClass); + + // If this lambda is an Expression and is suitable for hoisting, we rewrite this into a field access. + // + // Before: + // public Task ExecuteAsync(...) + // { + // ... + // Html.EditorFor(m => m.Price); + // ... + // } + // + // + // After: + // private static readonly Expression> __h0 = m => m.Price; + // public Task ExecuteAsync(...) + // { + // ... + // Html.EditorFor(__h0); + // ... + // } + // + var type = SemanticModel.GetTypeInfo(node); + + // Due to an anomaly where Roslyn (depending on code sample) may finish compilation without diagnostic + // errors (this code path does not execute when diagnostic errors are present) we need to validate that + // the ConvertedType was determined/is not null. + if (type.ConvertedType == null || + type.ConvertedType.Name != typeof(Expression).Name && + type.ConvertedType.ContainingNamespace.Name != typeof(Expression).Namespace) + { + return node; + } + + if (!node.Parent.IsKind(SyntaxKind.Argument)) + { + return node; + } + + var parameter = node.Parameter; + if (IsValidForHoisting(parameter, node.Body)) + { + // Replace with a MemberAccess + var memberName = string.Format(FieldNameTemplate, Expressions.Count); + var memberAccess = PadMemberAccess(node, SyntaxFactory.IdentifierName(memberName)); + Expressions.Add(new KeyValuePair(node, memberAccess)); + return memberAccess; + } + + return node; + } + + private static IdentifierNameSyntax PadMemberAccess( + SimpleLambdaExpressionSyntax node, + IdentifierNameSyntax memberAccess) + { + var charactersToExclude = memberAccess.Identifier.Text.Length; + var triviaList = new SyntaxTriviaList(); + + // Go through each token and + // 1. Append leading trivia + // 2. Append the same number of whitespace as the length of the token text + // 3. Append trailing trivia + foreach (var token in node.DescendantTokens()) + { + if (token.HasLeadingTrivia) + { + triviaList = triviaList.AddRange(token.LeadingTrivia); + } + + // Need to exclude the length of the member name from the padding. + var padding = token.Text.Length; + if (padding > charactersToExclude) + { + padding -= charactersToExclude; + charactersToExclude = 0; + } + else + { + charactersToExclude -= padding; + padding = 0; + } + + if (padding > 0) + { + triviaList = triviaList.Add(SyntaxFactory.Whitespace(new string(' ', padding))); + } + + if (token.HasTrailingTrivia) + { + triviaList = triviaList.AddRange(token.TrailingTrivia); + } + } + + return memberAccess + .WithLeadingTrivia(node.GetLeadingTrivia()) + .WithTrailingTrivia(triviaList); + } + + private static bool IsValidForHoisting(ParameterSyntax parameter, CSharpSyntaxNode node) + { + if (node.IsKind(SyntaxKind.IdentifierName)) + { + var identifier = (IdentifierNameSyntax)node; + if (identifier.Identifier.Text == parameter.Identifier.Text) + { + return true; + } + } + else if (node.IsKind(SyntaxKind.SimpleMemberAccessExpression)) + { + var memberAccess = (MemberAccessExpressionSyntax)node; + var lhs = memberAccess.Expression; + return IsValidForHoisting(parameter, lhs); + } + + return false; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs new file mode 100644 index 0000000000..52af4ae3f3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs @@ -0,0 +1,91 @@ +// Copyright (c) .NET 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.AspNetCore.Hosting; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class FileProviderRazorProjectFileSystem : RazorProjectFileSystem + { + private const string RazorFileExtension = ".cshtml"; + private readonly IFileProvider _provider; + private readonly IHostingEnvironment _hostingEnvironment; + + public FileProviderRazorProjectFileSystem(IRazorViewEngineFileProviderAccessor accessor, IHostingEnvironment hostingEnviroment) + { + if (accessor == null) + { + throw new ArgumentNullException(nameof(accessor)); + } + + if (hostingEnviroment == null) + { + throw new ArgumentNullException(nameof(hostingEnviroment)); + } + + _provider = accessor.FileProvider; + _hostingEnvironment = hostingEnviroment; + } + + public override RazorProjectItem GetItem(string path) + { + path = NormalizeAndEnsureValidPath(path); + var fileInfo = _provider.GetFileInfo(path); + + return new FileProviderRazorProjectItem(fileInfo, basePath: string.Empty, filePath: path, root: _hostingEnvironment.ContentRootPath); + } + + public override IEnumerable EnumerateItems(string path) + { + path = NormalizeAndEnsureValidPath(path); + return EnumerateFiles(_provider.GetDirectoryContents(path), path, prefix: string.Empty); + } + + private IEnumerable EnumerateFiles(IDirectoryContents directory, string basePath, string prefix) + { + if (directory.Exists) + { + foreach (var fileInfo in directory) + { + if (fileInfo.IsDirectory) + { + var relativePath = prefix + "/" + fileInfo.Name; + var subDirectory = _provider.GetDirectoryContents(JoinPath(basePath, relativePath)); + var children = EnumerateFiles(subDirectory, basePath, relativePath); + foreach (var child in children) + { + yield return child; + } + } + else if (string.Equals(RazorFileExtension, Path.GetExtension(fileInfo.Name), StringComparison.OrdinalIgnoreCase)) + { + var filePath = prefix + "/" + fileInfo.Name; + + yield return new FileProviderRazorProjectItem(fileInfo, basePath, filePath: filePath, root: _hostingEnvironment.ContentRootPath); + } + } + } + } + + private static string JoinPath(string path1, string path2) + { + var hasTrailingSlash = path1.EndsWith("/", StringComparison.Ordinal); + var hasLeadingSlash = path2.StartsWith("/", StringComparison.Ordinal); + if (hasLeadingSlash && hasTrailingSlash) + { + return path1 + path2.Substring(1); + } + else if (hasLeadingSlash || hasTrailingSlash) + { + return path1 + path2; + } + + return path1 + "/" + path2; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProjectItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProjectItem.cs new file mode 100644 index 0000000000..6108d6875b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProjectItem.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class FileProviderRazorProjectItem : RazorProjectItem + { + private string _root; + private string _relativePhysicalPath; + private bool _isRelativePhysicalPathSet; + + public FileProviderRazorProjectItem(IFileInfo fileInfo, string basePath, string filePath, string root) + { + FileInfo = fileInfo; + BasePath = basePath; + FilePath = filePath; + _root = root; + } + + public IFileInfo FileInfo { get; } + + public override string BasePath { get; } + + public override string FilePath { get; } + + public override bool Exists => FileInfo.Exists; + + public override string PhysicalPath => FileInfo.PhysicalPath; + + public override string RelativePhysicalPath + { + get + { + if (!_isRelativePhysicalPathSet) + { + _isRelativePhysicalPathSet = true; + + if (Exists) + { + if (_root != null && + !string.IsNullOrEmpty(PhysicalPath) && + PhysicalPath.StartsWith(_root, StringComparison.OrdinalIgnoreCase) && + PhysicalPath.Length > _root.Length && + (PhysicalPath[_root.Length] == Path.DirectorySeparatorChar || PhysicalPath[_root.Length] == Path.AltDirectorySeparatorChar)) + { + _relativePhysicalPath = PhysicalPath.Substring(_root.Length + 1); // Include leading separator + } + } + } + + return _relativePhysicalPath; + } + } + + public override Stream Read() + { + return FileInfo.CreateReadStream(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/IRazorViewEngineFileProviderAccessor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/IRazorViewEngineFileProviderAccessor.cs new file mode 100644 index 0000000000..670b7c3db3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/IRazorViewEngineFileProviderAccessor.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 Microsoft.Extensions.FileProviders; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// Accessor to the used by . + /// + public interface IRazorViewEngineFileProviderAccessor + { + /// + /// Gets the used to look up Razor files. + /// + IFileProvider FileProvider { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/LazyMetadataReferenceFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/LazyMetadataReferenceFeature.cs new file mode 100644 index 0000000000..ead4b29630 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/LazyMetadataReferenceFeature.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.Collections.Generic; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Razor; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class LazyMetadataReferenceFeature : IMetadataReferenceFeature + { + private readonly RazorReferenceManager _referenceManager; + + public LazyMetadataReferenceFeature(RazorReferenceManager referenceManager) + { + _referenceManager = referenceManager; + } + + /// + /// Invoking ensures that compilation + /// references are lazily evaluated. + /// + public IReadOnlyList References => _referenceManager.CompilationReferences; + + public RazorEngine Engine { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorDiagnosticSourceExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorDiagnosticSourceExtensions.cs new file mode 100644 index 0000000000..a60f5fb587 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorDiagnosticSourceExtensions.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.Diagnostics; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public static class MvcRazorDiagnosticSourceExtensions + { + public static void BeforeViewPage( + this DiagnosticSource diagnosticSource, + IRazorPage page, + ViewContext viewContext) + { + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.Razor.BeforeViewPage")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.Razor.BeforeViewPage", + new + { + page = page, + viewContext = viewContext, + actionDescriptor = viewContext.ActionDescriptor, + httpContext = viewContext.HttpContext, + }); + } + } + + public static void AfterViewPage( + this DiagnosticSource diagnosticSource, + IRazorPage page, + ViewContext viewContext) + { + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.Razor.AfterViewPage")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.Razor.AfterViewPage", + new + { + page = page, + viewContext = viewContext, + actionDescriptor = viewContext.ActionDescriptor, + httpContext = viewContext.HttpContext, + }); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorLoggerExtensions.cs new file mode 100644 index 0000000000..21d7496abd --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorLoggerExtensions.cs @@ -0,0 +1,212 @@ +// Copyright (c) .NET 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.Linq; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public static class MvcRazorLoggerExtensions + { + private static readonly double TimestampToTicks = TimeSpan.TicksPerSecond / (double)Stopwatch.Frequency; + + private static readonly Action _generatedCodeToAssemblyCompilationStart; + private static readonly Action _generatedCodeToAssemblyCompilationEnd; + + private static readonly Action _viewCompilerStartCodeGeneration; + private static readonly Action _viewCompilerEndCodeGeneration; + private static readonly Action _viewCompilerLocatedCompiledView; + private static readonly Action _viewCompilerNoCompiledViewsFound; + private static readonly Action _viewCompilerLocatedCompiledViewForPath; + private static readonly Action _viewCompilerRecompilingCompiledView; + private static readonly Action _viewCompilerCouldNotFindFileToCompileForPath; + private static readonly Action _viewCompilerFoundFileToCompileForPath; + private static readonly Action _viewCompilerInvalidatingCompiledFile; + + private static readonly Action _viewLookupCacheMiss; + private static readonly Action _viewLookupCacheHit; + private static readonly Action _precompiledViewFound; + + private static readonly Action _tagHelperComponentInitialized; + private static readonly Action _tagHelperComponentProcessed; + + + static MvcRazorLoggerExtensions() + { + _viewCompilerStartCodeGeneration = LoggerMessage.Define( + LogLevel.Debug, + 1, + "Code generation for the Razor file at '{FilePath}' started."); + + _viewCompilerEndCodeGeneration = LoggerMessage.Define( + LogLevel.Debug, + 2, + "Code generation for the Razor file at '{FilePath}' completed in {ElapsedMilliseconds}ms."); + + _viewCompilerLocatedCompiledView = LoggerMessage.Define( + LogLevel.Debug, + 3, + "Initializing Razor view compiler with compiled view: '{ViewName}'."); + + _viewCompilerNoCompiledViewsFound = LoggerMessage.Define( + LogLevel.Debug, + 4, + "Initializing Razor view compiler with no compiled views."); + + _viewCompilerLocatedCompiledViewForPath = LoggerMessage.Define( + LogLevel.Trace, + 5, + "Located compiled view for view at path '{Path}'."); + + _viewCompilerLocatedCompiledViewForPath = LoggerMessage.Define( + LogLevel.Trace, + 5, + "Located compiled view for view at path '{Path}'."); + + _viewCompilerRecompilingCompiledView = LoggerMessage.Define( + LogLevel.Trace, + 6, + "Invalidating compiled view for view at path '{Path}'."); + + _viewCompilerCouldNotFindFileToCompileForPath = LoggerMessage.Define( + LogLevel.Trace, + 7, + "Could not find a file for view at path '{Path}'."); + + _viewCompilerFoundFileToCompileForPath = LoggerMessage.Define( + LogLevel.Trace, + 8, + "Found file at path '{Path}'."); + + _viewCompilerInvalidatingCompiledFile = LoggerMessage.Define( + LogLevel.Trace, + 9, + "Invalidating compiled view at path '{Path}' with a file since the checksum did not match."); + + _viewLookupCacheMiss = LoggerMessage.Define( + LogLevel.Debug, + 1, + "View lookup cache miss for view '{ViewName}' in controller '{ControllerName}'."); + + _viewLookupCacheHit = LoggerMessage.Define( + LogLevel.Debug, + 2, + "View lookup cache hit for view '{ViewName}' in controller '{ControllerName}'."); + + _precompiledViewFound = LoggerMessage.Define( + LogLevel.Debug, + 3, + "Using precompiled view for '{RelativePath}'."); + + _generatedCodeToAssemblyCompilationStart = LoggerMessage.Define( + LogLevel.Debug, + 1, + "Compilation of the generated code for the Razor file at '{FilePath}' started."); + + _generatedCodeToAssemblyCompilationEnd = LoggerMessage.Define( + LogLevel.Debug, + 2, + "Compilation of the generated code for the Razor file at '{FilePath}' completed in {ElapsedMilliseconds}ms."); + + _tagHelperComponentInitialized = LoggerMessage.Define( + LogLevel.Debug, + 2, + "Tag helper component '{ComponentName}' initialized."); + + _tagHelperComponentProcessed = LoggerMessage.Define( + LogLevel.Debug, + 3, + "Tag helper component '{ComponentName}' processed."); + } + + public static void ViewCompilerStartCodeGeneration(this ILogger logger, string filePath) + { + _viewCompilerStartCodeGeneration(logger, filePath, null); + } + + public static void ViewCompilerEndCodeGeneration(this ILogger logger, string filePath, long startTimestamp) + { + // Don't log if logging wasn't enabled at start of request as time will be wildly wrong. + if (startTimestamp != 0) + { + var currentTimestamp = Stopwatch.GetTimestamp(); + var elapsed = new TimeSpan((long)(TimestampToTicks * (currentTimestamp - startTimestamp))); + _viewCompilerEndCodeGeneration(logger, filePath, elapsed.TotalMilliseconds, null); + } + } + + public static void ViewCompilerLocatedCompiledView(this ILogger logger, string view) + { + _viewCompilerLocatedCompiledView(logger, view, null); + } + + public static void ViewCompilerNoCompiledViewsFound(this ILogger logger) + { + _viewCompilerNoCompiledViewsFound(logger, null); + } + + public static void ViewCompilerLocatedCompiledViewForPath(this ILogger logger, string path) + { + _viewCompilerLocatedCompiledViewForPath(logger, path, null); + } + + public static void ViewCompilerCouldNotFindFileAtPath(this ILogger logger, string path) + { + _viewCompilerCouldNotFindFileToCompileForPath(logger, path, null); + } + + public static void ViewCompilerFoundFileToCompile(this ILogger logger, string path) + { + _viewCompilerFoundFileToCompileForPath(logger, path, null); + } + + public static void ViewCompilerInvalidingCompiledFile(this ILogger logger, string path) + { + _viewCompilerInvalidatingCompiledFile(logger, path, null); + } + + public static void ViewLookupCacheMiss(this ILogger logger, string viewName, string controllerName) + { + _viewLookupCacheMiss(logger, viewName, controllerName, null); + } + + public static void ViewLookupCacheHit(this ILogger logger, string viewName, string controllerName) + { + _viewLookupCacheHit(logger, viewName, controllerName, null); + } + + public static void PrecompiledViewFound(this ILogger logger, string relativePath) + { + _precompiledViewFound(logger, relativePath, null); + } + + public static void GeneratedCodeToAssemblyCompilationStart(this ILogger logger, string filePath) + { + _generatedCodeToAssemblyCompilationStart(logger, filePath, null); + } + + public static void TagHelperComponentInitialized(this ILogger logger, string componentName) + { + _tagHelperComponentInitialized(logger, componentName, null); + } + + public static void TagHelperComponentProcessed(this ILogger logger, string componentName) + { + _tagHelperComponentProcessed(logger, componentName, null); + } + + public static void GeneratedCodeToAssemblyCompilationEnd(this ILogger logger, string filePath, long startTimestamp) + { + // Don't log if logging wasn't enabled at start of request as time will be wildly wrong. + if (startTimestamp != 0) + { + var currentTimestamp = Stopwatch.GetTimestamp(); + var elapsed = new TimeSpan((long)(TimestampToTicks * (currentTimestamp - startTimestamp))); + _generatedCodeToAssemblyCompilationEnd(logger, filePath, elapsed.TotalMilliseconds, null); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorMvcViewOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorMvcViewOptionsSetup.cs new file mode 100644 index 0000000000..8b35da8ddf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorMvcViewOptionsSetup.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.Razor.Internal +{ + /// + /// Configures to use . + /// + public class MvcRazorMvcViewOptionsSetup : IConfigureOptions + { + private readonly IRazorViewEngine _razorViewEngine; + + /// + /// Initializes a new instance of . + /// + /// The . + public MvcRazorMvcViewOptionsSetup(IRazorViewEngine razorViewEngine) + { + if (razorViewEngine == null) + { + throw new ArgumentNullException(nameof(razorViewEngine)); + } + + _razorViewEngine = razorViewEngine; + } + + /// + /// Configures to use . + /// + /// The to configure. + public void Configure(MvcViewOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + options.ViewEngines.Add(_razorViewEngine); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorInjectAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorInjectAttribute.cs new file mode 100644 index 0000000000..72b1c31f49 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorInjectAttribute.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.Razor.Internal +{ + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class RazorInjectAttribute : Attribute + { + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorPagePropertyActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorPagePropertyActivator.cs new file mode 100644 index 0000000000..b163088361 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorPagePropertyActivator.cs @@ -0,0 +1,151 @@ +// Copyright (c) .NET 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.Reflection; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class RazorPagePropertyActivator + { + private readonly IModelMetadataProvider _metadataProvider; + private readonly Func _rootFactory; + private readonly Func _nestedFactory; + private readonly Type _viewDataDictionaryType; + private readonly PropertyActivator[] _propertyActivators; + + public RazorPagePropertyActivator( + Type pageType, + Type declaredModelType, + IModelMetadataProvider metadataProvider, + PropertyValueAccessors propertyValueAccessors) + { + _metadataProvider = metadataProvider; + + // In the absence of a model on the current type, we'll attempt to use ViewDataDictionary on the current type. + var viewDataDictionaryModelType = declaredModelType ?? typeof(object); + + if (viewDataDictionaryModelType != null) + { + _viewDataDictionaryType = typeof(ViewDataDictionary<>).MakeGenericType(viewDataDictionaryModelType); + _rootFactory = ViewDataDictionaryFactory.CreateFactory(viewDataDictionaryModelType.GetTypeInfo()); + _nestedFactory = ViewDataDictionaryFactory.CreateNestedFactory(viewDataDictionaryModelType.GetTypeInfo()); + } + + _propertyActivators = PropertyActivator.GetPropertiesToActivate( + pageType, + typeof(RazorInjectAttribute), + propertyInfo => CreateActivateInfo(propertyInfo, propertyValueAccessors), + includeNonPublic: true); + } + + public void Activate(object page, ViewContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (_viewDataDictionaryType != null) + { + context.ViewData = CreateViewDataDictionary(context); + } + + for (var i = 0; i < _propertyActivators.Length; i++) + { + var activateInfo = _propertyActivators[i]; + activateInfo.Activate(page, context); + } + } + + // Internal for unit testing. + internal ViewDataDictionary CreateViewDataDictionary(ViewContext context) + { + // Create a ViewDataDictionary if the ViewContext.ViewData is not set or the type of + // ViewContext.ViewData is an incompatible type. + if (context.ViewData == null) + { + // Create ViewDataDictionary(IModelMetadataProvider, ModelStateDictionary). + return _rootFactory(_metadataProvider, context.ModelState); + } + else if (context.ViewData.GetType() != _viewDataDictionaryType) + { + // Create ViewDataDictionary(ViewDataDictionary). + return _nestedFactory(context.ViewData); + } + + return context.ViewData; + } + + private static PropertyActivator CreateActivateInfo( + PropertyInfo property, + PropertyValueAccessors valueAccessors) + { + Func valueAccessor; + if (typeof(ViewDataDictionary).IsAssignableFrom(property.PropertyType)) + { + // Logic looks reversed in condition above but is OK. Support only properties of base + // ViewDataDictionary type and activationInfo.ViewDataDictionaryType. VDD will fail when + // assigning to the property (InvalidCastException) and that's fine. + valueAccessor = context => context.ViewData; + } + else if (property.PropertyType == typeof(IUrlHelper)) + { + // W.r.t. specificity of above condition: Users are much more likely to inject their own + // IUrlHelperFactory than to create a class implementing IUrlHelper (or a sub-interface) and inject + // that. But the second scenario is supported. (Note the class must implement ICanHasViewContext.) + valueAccessor = valueAccessors.UrlHelperAccessor; + } + else if (property.PropertyType == typeof(IJsonHelper)) + { + valueAccessor = valueAccessors.JsonHelperAccessor; + } + else if (property.PropertyType == typeof(DiagnosticSource)) + { + valueAccessor = valueAccessors.DiagnosticSourceAccessor; + } + else if (property.PropertyType == typeof(HtmlEncoder)) + { + valueAccessor = valueAccessors.HtmlEncoderAccessor; + } + else if (property.PropertyType == typeof(IModelExpressionProvider)) + { + valueAccessor = valueAccessors.ModelExpressionProviderAccessor; + } + else + { + valueAccessor = context => + { + var serviceProvider = context.HttpContext.RequestServices; + var value = serviceProvider.GetRequiredService(property.PropertyType); + (value as IViewContextAware)?.Contextualize(context); + + return value; + }; + } + + return new PropertyActivator(property, valueAccessor); + } + + public class PropertyValueAccessors + { + public Func UrlHelperAccessor { get; set; } + + public Func JsonHelperAccessor { get; set; } + + public Func DiagnosticSourceAccessor { get; set; } + + public Func HtmlEncoderAccessor { get; set; } + + public Func ModelExpressionProviderAccessor { get; set; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs new file mode 100644 index 0000000000..93b871cc76 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs @@ -0,0 +1,446 @@ +// Copyright (c) .NET 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.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Razor.Hosting; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Emit; +using Microsoft.CodeAnalysis.Text; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// Caches the result of runtime compilation of Razor files for the duration of the application lifetime. + /// + public class RazorViewCompiler : IViewCompiler + { + private readonly object _cacheLock = new object(); + private readonly Dictionary _precompiledViews; + private readonly ConcurrentDictionary _normalizedPathCache; + private readonly IFileProvider _fileProvider; + private readonly RazorProjectEngine _projectEngine; + private readonly Action _compilationCallback; + private readonly ILogger _logger; + private readonly CSharpCompiler _csharpCompiler; + private readonly IMemoryCache _cache; + + public RazorViewCompiler( + IFileProvider fileProvider, + RazorProjectEngine projectEngine, + CSharpCompiler csharpCompiler, + Action compilationCallback, + IList precompiledViews, + ILogger logger) + { + if (fileProvider == null) + { + throw new ArgumentNullException(nameof(fileProvider)); + } + + if (projectEngine == null) + { + throw new ArgumentNullException(nameof(projectEngine)); + } + + if (csharpCompiler == null) + { + throw new ArgumentNullException(nameof(csharpCompiler)); + } + + if (compilationCallback == null) + { + throw new ArgumentNullException(nameof(compilationCallback)); + } + + if (precompiledViews == null) + { + throw new ArgumentNullException(nameof(precompiledViews)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + _fileProvider = fileProvider; + _projectEngine = projectEngine; + _csharpCompiler = csharpCompiler; + _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()); + + // 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 + // problems when deploying to linux. Rather than deal with these issues, we just don't support + // views that differ only by case. + _precompiledViews = new Dictionary( + precompiledViews.Count, + StringComparer.OrdinalIgnoreCase); + + foreach (var precompiledView in precompiledViews) + { + logger.ViewCompilerLocatedCompiledView(precompiledView.RelativePath); + + if (!_precompiledViews.ContainsKey(precompiledView.RelativePath)) + { + // View ordering has precedence semantics, a view with a higher precedence was + // already added to the list. + _precompiledViews.Add(precompiledView.RelativePath, precompiledView); + } + } + + if (_precompiledViews.Count == 0) + { + logger.ViewCompilerNoCompiledViewsFound(); + } + } + + /// + public Task CompileAsync(string relativePath) + { + if (relativePath == null) + { + throw new ArgumentNullException(nameof(relativePath)); + } + + // Attempt to lookup the cache entry using the passed in path. This will succeed if the path is already + // normalized and a cache entry exists. + if (_cache.TryGetValue(relativePath, out Task cachedResult)) + { + return cachedResult; + } + + var normalizedPath = GetNormalizedPath(relativePath); + if (_cache.TryGetValue(normalizedPath, out cachedResult)) + { + return cachedResult; + } + + // Entry does not exist. Attempt to create one. + cachedResult = OnCacheMiss(normalizedPath); + return cachedResult; + } + + private Task OnCacheMiss(string normalizedPath) + { + ViewCompilerWorkItem item; + TaskCompletionSource taskSource; + MemoryCacheEntryOptions cacheEntryOptions; + + // Safe races cannot be allowed when compiling Razor pages. To ensure only one compilation request succeeds + // per file, we'll lock the creation of a cache entry. Creating the cache entry should be very quick. The + // actual work for compiling files happens outside the critical section. + lock (_cacheLock) + { + // Double-checked locking to handle a possible race. + if (_cache.TryGetValue(normalizedPath, out Task result)) + { + return result; + } + + if (_precompiledViews.TryGetValue(normalizedPath, out var precompiledView)) + { + _logger.ViewCompilerLocatedCompiledViewForPath(normalizedPath); + item = CreatePrecompiledWorkItem(normalizedPath, precompiledView); + } + else + { + item = CreateRuntimeCompilationWorkItem(normalizedPath); + } + + // At this point, we've decided what to do - but we should create the cache entry and + // release the lock first. + cacheEntryOptions = new MemoryCacheEntryOptions(); + + Debug.Assert(item.ExpirationTokens != null); + for (var i = 0; i < item.ExpirationTokens.Count; i++) + { + cacheEntryOptions.ExpirationTokens.Add(item.ExpirationTokens[i]); + } + + taskSource = new TaskCompletionSource(); + if (item.SupportsCompilation) + { + // We'll compile in just a sec, be patient. + } + else + { + // If we can't compile, we should have already created the descriptor + Debug.Assert(item.Descriptor != null); + taskSource.SetResult(item.Descriptor); + } + + _cache.Set(normalizedPath, taskSource.Task, cacheEntryOptions); + } + + // Now the lock has been released so we can do more expensive processing. + if (item.SupportsCompilation) + { + Debug.Assert(taskSource != null); + + if (item.Descriptor?.Item != null && + ChecksumValidator.IsItemValid(_projectEngine.FileSystem, item.Descriptor.Item)) + { + // If the item has checksums to validate, we should also have a precompiled view. + Debug.Assert(item.Descriptor != null); + + taskSource.SetResult(item.Descriptor); + return taskSource.Task; + } + + _logger.ViewCompilerInvalidingCompiledFile(item.NormalizedPath); + try + { + var descriptor = CompileAndEmit(normalizedPath); + descriptor.ExpirationTokens = cacheEntryOptions.ExpirationTokens; + taskSource.SetResult(descriptor); + } + catch (Exception ex) + { + taskSource.SetException(ex); + } + } + + return taskSource.Task; + } + + private ViewCompilerWorkItem CreatePrecompiledWorkItem(string normalizedPath, CompiledViewDescriptor precompiledView) + { + // We have a precompiled view - but we're not sure that we can use it yet. + // + // We need to determine first if we have enough information to 'recompile' this view. If that's the case + // we'll create change tokens for all of the files. + // + // Then we'll attempt to validate if any of those files have different content than the original sources + // based on checksums. + if (precompiledView.Item == null || !ChecksumValidator.IsRecompilationSupported(precompiledView.Item)) + { + return new ViewCompilerWorkItem() + { + // If we don't have a checksum for the primary source file we can't recompile. + SupportsCompilation = false, + + ExpirationTokens = Array.Empty(), // Never expire because we can't recompile. + Descriptor = precompiledView, // This will be used as-is. + }; + } + + var item = new ViewCompilerWorkItem() + { + SupportsCompilation = true, + + Descriptor = precompiledView, // This might be used, if the checksums match. + + // 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)); + } + + // 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 + // the L2 cache). + item.Descriptor = new CompiledViewDescriptor() + { + ExpirationTokens = item.ExpirationTokens, + IsPrecompiled = true, + Item = precompiledView.Item, + RelativePath = precompiledView.RelativePath, + ViewAttribute = precompiledView.ViewAttribute, + }; + + return item; + } + + private ViewCompilerWorkItem CreateRuntimeCompilationWorkItem(string normalizedPath) + { + var expirationTokens = new List() + { + _fileProvider.Watch(normalizedPath), + }; + + var projectItem = _projectEngine.FileSystem.GetItem(normalizedPath); + if (!projectItem.Exists) + { + _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 + // is added. + return new ViewCompilerWorkItem() + { + // We don't have enough information to compile + SupportsCompilation = false, + + Descriptor = new CompiledViewDescriptor() + { + RelativePath = normalizedPath, + ExpirationTokens = expirationTokens, + }, + + // We can try again if the file gets created. + ExpirationTokens = expirationTokens, + }; + } + + _logger.ViewCompilerFoundFileToCompile(normalizedPath); + + // 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. + // In that case once we attempt to parse the Razor file we'll explode and give the a user a decent + // error message; for now, lets just be extra protective and assume 0 imports to not give a bad error. + var imports = importFeature?.GetImports(projectItem) ?? Enumerable.Empty(); + var physicalImports = imports.Where(import => import.FilePath != null); + + // Now that we have non-dynamic imports we need to get their RazorProjectItem equivalents so we have their + // physical file paths (according to the FileSystem). + foreach (var physicalImport in physicalImports) + { + expirationTokens.Add(_fileProvider.Watch(physicalImport.FilePath)); + } + + return new ViewCompilerWorkItem() + { + SupportsCompilation = true, + + NormalizedPath = normalizedPath, + ExpirationTokens = expirationTokens, + }; + } + + protected virtual CompiledViewDescriptor CompileAndEmit(string relativePath) + { + var projectItem = _projectEngine.FileSystem.GetItem(relativePath); + var codeDocument = _projectEngine.Process(projectItem); + var cSharpDocument = codeDocument.GetCSharpDocument(); + + if (cSharpDocument.Diagnostics.Count > 0) + { + throw CompilationFailedExceptionFactory.Create( + codeDocument, + cSharpDocument.Diagnostics); + } + + var assembly = CompileAndEmit(codeDocument, cSharpDocument.GeneratedCode); + + // Anything we compile from source will use Razor 2.1 and so should have the new metadata. + var loader = new RazorCompiledItemLoader(); + var item = loader.LoadItems(assembly).SingleOrDefault(); + var attribute = assembly.GetCustomAttribute(); + + return new CompiledViewDescriptor(item, attribute); + } + + internal Assembly CompileAndEmit(RazorCodeDocument codeDocument, string generatedCode) + { + _logger.GeneratedCodeToAssemblyCompilationStart(codeDocument.Source.FilePath); + + var startTimestamp = _logger.IsEnabled(LogLevel.Debug) ? Stopwatch.GetTimestamp() : 0; + + var assemblyName = Path.GetRandomFileName(); + var compilation = CreateCompilation(generatedCode, assemblyName); + + var emitOptions = _csharpCompiler.EmitOptions; + var emitPdbFile = _csharpCompiler.EmitPdb && emitOptions.DebugInformationFormat != DebugInformationFormat.Embedded; + + using (var assemblyStream = new MemoryStream()) + using (var pdbStream = emitPdbFile ? new MemoryStream() : null) + { + var result = compilation.Emit( + assemblyStream, + pdbStream, + options: emitOptions); + + if (!result.Success) + { + throw CompilationFailedExceptionFactory.Create( + codeDocument, + generatedCode, + assemblyName, + result.Diagnostics); + } + + assemblyStream.Seek(0, SeekOrigin.Begin); + pdbStream?.Seek(0, SeekOrigin.Begin); + + var assembly = Assembly.Load(assemblyStream.ToArray(), pdbStream?.ToArray()); + _logger.GeneratedCodeToAssemblyCompilationEnd(codeDocument.Source.FilePath, startTimestamp); + + return assembly; + } + } + + private CSharpCompilation CreateCompilation(string compilationContent, string assemblyName) + { + var sourceText = SourceText.From(compilationContent, Encoding.UTF8); + var syntaxTree = _csharpCompiler.CreateSyntaxTree(sourceText).WithFilePath(assemblyName); + var compilation = _csharpCompiler + .CreateCompilation(assemblyName) + .AddSyntaxTrees(syntaxTree); + compilation = ExpressionRewriter.Rewrite(compilation); + + var compilationContext = new RoslynCompilationContext(compilation); + _compilationCallback(compilationContext); + compilation = compilationContext.Compilation; + return compilation; + } + + private string GetNormalizedPath(string relativePath) + { + Debug.Assert(relativePath != null); + if (relativePath.Length == 0) + { + return relativePath; + } + + if (!_normalizedPathCache.TryGetValue(relativePath, out var normalizedPath)) + { + normalizedPath = ViewPath.NormalizePath(relativePath); + _normalizedPathCache[relativePath] = normalizedPath; + } + + return normalizedPath; + } + + private class ViewCompilerWorkItem + { + public bool SupportsCompilation { get; set; } + + public string NormalizedPath { get; set; } + + public IList ExpirationTokens { get; set; } + + public CompiledViewDescriptor Descriptor { get; set; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs new file mode 100644 index 0000000000..c705a88aac --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.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; +using System.Threading; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public class RazorViewCompilerProvider : IViewCompilerProvider + { + private readonly RazorProjectEngine _razorProjectEngine; + private readonly ApplicationPartManager _applicationPartManager; + private readonly IRazorViewEngineFileProviderAccessor _fileProviderAccessor; + private readonly CSharpCompiler _csharpCompiler; + private readonly RazorViewEngineOptions _viewEngineOptions; + private readonly ILogger _logger; + private readonly Func _createCompiler; + + private object _initializeLock = new object(); + private bool _initialized; + private IViewCompiler _compiler; + + public RazorViewCompilerProvider( + ApplicationPartManager applicationPartManager, + RazorProjectEngine razorProjectEngine, + IRazorViewEngineFileProviderAccessor fileProviderAccessor, + CSharpCompiler csharpCompiler, + IOptions viewEngineOptionsAccessor, + ILoggerFactory loggerFactory) + { + _applicationPartManager = applicationPartManager; + _razorProjectEngine = razorProjectEngine; + _fileProviderAccessor = fileProviderAccessor; + _csharpCompiler = csharpCompiler; + _viewEngineOptions = viewEngineOptionsAccessor.Value; + + _logger = loggerFactory.CreateLogger(); + _createCompiler = CreateCompiler; + } + + public IViewCompiler GetCompiler() + { + var fileProvider = _fileProviderAccessor.FileProvider; + if (fileProvider is NullFileProvider) + { + var message = Resources.FormatFileProvidersAreRequired( + typeof(RazorViewEngineOptions).FullName, + nameof(RazorViewEngineOptions.FileProviders), + typeof(IFileProvider).FullName); + throw new InvalidOperationException(message); + } + + return LazyInitializer.EnsureInitialized( + ref _compiler, + ref _initialized, + ref _initializeLock, + _createCompiler); + } + + private IViewCompiler CreateCompiler() + { + var feature = new ViewsFeature(); + _applicationPartManager.PopulateFeature(feature); + + return new RazorViewCompiler( + _fileProviderAccessor.FileProvider, + _razorProjectEngine, + _csharpCompiler, + _viewEngineOptions.CompilationCallback, + feature.ViewDescriptors, + _logger); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewEngineOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewEngineOptionsSetup.cs new file mode 100644 index 0000000000..e8c49113ba --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewEngineOptionsSetup.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; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// Sets up default options for . + /// + public class RazorViewEngineOptionsSetup : IConfigureOptions + { + private readonly IHostingEnvironment _hostingEnvironment; + + /// + /// Initializes a new instance of . + /// + /// for the application. + public RazorViewEngineOptionsSetup(IHostingEnvironment hostingEnvironment) + { + _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); + } + + public void Configure(RazorViewEngineOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (_hostingEnvironment.ContentRootFileProvider != null) + { + options.FileProviders.Add(_hostingEnvironment.ContentRootFileProvider); + } + + options.ViewLocationFormats.Add("/Views/{1}/{0}" + RazorViewEngine.ViewExtension); + options.ViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension); + + 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); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ServiceBasedTagHelperActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ServiceBasedTagHelperActivator.cs new file mode 100644 index 0000000000..d5ba1e6409 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ServiceBasedTagHelperActivator.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 System; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// A that retrieves tag helpers as services from the request's + /// . + /// + public class ServiceBasedTagHelperActivator : ITagHelperActivator + { + /// + public TTagHelper Create(ViewContext context) where TTagHelper : ITagHelper + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + return context.HttpContext.RequestServices.GetRequiredService(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/SymbolsUtility.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/SymbolsUtility.cs new file mode 100644 index 0000000000..280fe8c84a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/SymbolsUtility.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 System.Runtime.InteropServices; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// Utility type for determining if a platform supports full pdb file generation. + /// + internal static class SymbolsUtility + { + // Native pdb writer's CLSID + private const string SymWriterGuid = "0AE2DEB0-F901-478b-BB9F-881EE8066788"; + + /// + /// Determines if the current platform supports full pdb generation. + /// + /// true if full pdb generation is supported; false otherwise. + public static bool SupportsFullPdbGeneration() + { + if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + // Cross-plat always produce portable pdbs. + return false; + } + + if (Type.GetType("Mono.Runtime") != null) + { + return false; + } + + try + { + // Check for the pdb writer component that roslyn uses to generate pdbs + var type = Marshal.GetTypeFromCLSID(new Guid(SymWriterGuid)); + if (type != null) + { + // This line will throw if pdb generation is not supported. + Activator.CreateInstance(type); + return true; + } + } + catch + { + } + + return false; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelperComponentManager.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelperComponentManager.cs new file mode 100644 index 0000000000..38dde4f941 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelperComponentManager.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 System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// The default implementation of the . + /// + public class TagHelperComponentManager : ITagHelperComponentManager + { + /// + /// Creates a new . + /// + /// The collection of s. + public TagHelperComponentManager(IEnumerable tagHelperComponents) + { + if (tagHelperComponents == null) + { + throw new ArgumentNullException(nameof(tagHelperComponents)); + } + + Components = new List(tagHelperComponents); + } + + /// + public ICollection Components { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelpersAsServices.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelpersAsServices.cs new file mode 100644 index 0000000000..a441c75de1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelpersAsServices.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.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + public static class TagHelpersAsServices + { + public static void AddTagHelpersAsServices(ApplicationPartManager manager, IServiceCollection services) + { + if (manager == null) + { + throw new ArgumentNullException(nameof(manager)); + } + + if (services == null) + { + throw new ArgumentNullException(nameof(services)); + } + + var feature = new TagHelperFeature(); + manager.PopulateFeature(feature); + + foreach (var type in feature.TagHelpers.Select(t => t.AsType())) + { + services.TryAddTransient(type, type); + } + + services.Replace(ServiceDescriptor.Transient()); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheItem.cs new file mode 100644 index 0000000000..23fe788101 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheItem.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.Razor.Internal +{ + /// + /// An item in . + /// + public struct ViewLocationCacheItem + { + /// + /// Initializes a new instance of . + /// + /// The factory. + /// The application relative path of the . + public ViewLocationCacheItem(Func razorPageFactory, string location) + { + PageFactory = razorPageFactory; + Location = location; + } + + /// + /// Gets the application relative path of the + /// + public string Location { get; } + + /// + /// Gets the factory. + /// + public Func PageFactory { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs new file mode 100644 index 0000000000..7643882f79 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs @@ -0,0 +1,158 @@ +// Copyright (c) .NET 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.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// Key for entries in . + /// + public struct ViewLocationCacheKey : IEquatable + { + /// + /// Initializes a new instance of . + /// + /// The view name or path. + /// Determines if the page being found is the main page for an action. + public ViewLocationCacheKey( + string viewName, + bool isMainPage) + : this( + viewName, + controllerName: null, + areaName: null, + pageName: null, + isMainPage: isMainPage, + values: null) + { + } + + /// + /// Initializes a new instance of . + /// + /// The view name. + /// The controller name. + /// The area name. + /// The page name. + /// Determines if the page being found is the main page for an action. + /// Values from instances. + public ViewLocationCacheKey( + string viewName, + string controllerName, + string areaName, + string pageName, + bool isMainPage, + IReadOnlyDictionary values) + { + ViewName = viewName; + ControllerName = controllerName; + AreaName = areaName; + PageName = pageName; + IsMainPage = isMainPage; + ViewLocationExpanderValues = values; + } + + /// + /// Gets the view name. + /// + public string ViewName { get; } + + /// + /// Gets the controller name. + /// + public string ControllerName { get; } + + /// + /// Gets the area name. + /// + public string AreaName { get; } + + /// + /// Gets the page name. + /// + public string PageName { get; } + + /// + /// Determines if the page being found is the main page for an action. + /// + public bool IsMainPage { get; } + + /// + /// Gets the values populated by instances. + /// + public IReadOnlyDictionary ViewLocationExpanderValues { get; } + + /// + public bool Equals(ViewLocationCacheKey y) + { + if (IsMainPage != y.IsMainPage || + !string.Equals(ViewName, y.ViewName, StringComparison.Ordinal) || + !string.Equals(ControllerName, y.ControllerName, StringComparison.Ordinal) || + !string.Equals(AreaName, y.AreaName, StringComparison.Ordinal) || + !string.Equals(PageName, y.PageName, StringComparison.Ordinal)) + { + return false; + } + + if (ReferenceEquals(ViewLocationExpanderValues, y.ViewLocationExpanderValues)) + { + return true; + } + + if (ViewLocationExpanderValues == null || + y.ViewLocationExpanderValues == null || + (ViewLocationExpanderValues.Count != y.ViewLocationExpanderValues.Count)) + { + return false; + } + + foreach (var item in ViewLocationExpanderValues) + { + string yValue; + if (!y.ViewLocationExpanderValues.TryGetValue(item.Key, out yValue) || + !string.Equals(item.Value, yValue, StringComparison.Ordinal)) + { + return false; + } + } + + return true; + } + + /// + public override bool Equals(object obj) + { + if (obj is ViewLocationCacheKey) + { + return Equals((ViewLocationCacheKey)obj); + } + + return false; + } + + /// + public override int GetHashCode() + { + var hashCodeCombiner = HashCodeCombiner.Start(); + hashCodeCombiner.Add(IsMainPage ? 1 : 0); + hashCodeCombiner.Add(ViewName, StringComparer.Ordinal); + hashCodeCombiner.Add(ControllerName, StringComparer.Ordinal); + hashCodeCombiner.Add(AreaName, StringComparer.Ordinal); + hashCodeCombiner.Add(PageName, StringComparer.Ordinal); + + if (ViewLocationExpanderValues != null) + { + foreach (var item in ViewLocationExpanderValues) + { + hashCodeCombiner.Add(item.Key, StringComparer.Ordinal); + hashCodeCombiner.Add(item.Value, StringComparer.Ordinal); + } + } + + return hashCodeCombiner; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheResult.cs new file mode 100644 index 0000000000..c703bcaf19 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheResult.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + /// + /// Result of view location cache lookup. + /// + public class ViewLocationCacheResult + { + /// + /// Initializes a new instance of + /// for a view that was successfully found at the specified location. + /// + /// The for the found view. + /// s for applicable _ViewStarts. + public ViewLocationCacheResult( + ViewLocationCacheItem view, + IReadOnlyList viewStarts) + { + if (viewStarts == null) + { + throw new ArgumentNullException(nameof(viewStarts)); + } + + ViewEntry = view; + ViewStartEntries = viewStarts; + Success = true; + } + + /// + /// Initializes a new instance of for a + /// failed view lookup. + /// + /// Locations that were searched. + public ViewLocationCacheResult(IEnumerable searchedLocations) + { + if (searchedLocations == null) + { + throw new ArgumentNullException(nameof(searchedLocations)); + } + + SearchedLocations = searchedLocations; + } + + /// + /// for the located view. + /// + /// null if is false. + public ViewLocationCacheItem ViewEntry { get; } + + /// + /// s for applicable _ViewStarts. + /// + /// null if is false. + public IReadOnlyList ViewStartEntries { get; } + + /// + /// The sequence of locations that were searched. + /// + /// + /// When is true this includes all paths that were search prior to finding + /// a view at . When is false, this includes + /// all search paths. + /// + public IEnumerable SearchedLocations { get; } + + /// + /// Gets a value that indicates whether the view was successfully found. + /// + public bool Success { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewPath.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewPath.cs new file mode 100644 index 0000000000..813493ee76 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewPath.cs @@ -0,0 +1,45 @@ +// Copyright (c) .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.Razor.Internal +{ + public static class ViewPath + { + public static string NormalizePath(string path) + { + var addLeadingSlash = path[0] != '\\' && path[0] != '/'; + var transformSlashes = path.IndexOf('\\') != -1; + + if (!addLeadingSlash && !transformSlashes) + { + return path; + } + + var length = path.Length; + if (addLeadingSlash) + { + length++; + } + + var builder = new InplaceStringBuilder(length); + if (addLeadingSlash) + { + builder.Append('/'); + } + + for (var i = 0; i < path.Length; i++) + { + var ch = path[i]; + if (ch == '\\') + { + ch = '/'; + } + builder.Append(ch); + } + + return builder.ToString(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpander.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpander.cs new file mode 100644 index 0000000000..1ed92110bc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpander.cs @@ -0,0 +1,115 @@ +// Copyright (c) .NET 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; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// A that adds the language as an extension prefix to view names. Language + /// that is getting added as extension prefix comes from . + /// + /// + /// For the default case with no areas, views are generated with the following patterns (assuming controller is + /// "Home", action is "Index" and language is "en") + /// Views/Home/en/Action + /// Views/Home/Action + /// Views/Shared/en/Action + /// Views/Shared/Action + /// + public class LanguageViewLocationExpander : IViewLocationExpander + { + private const string ValueKey = "language"; + private readonly LanguageViewLocationExpanderFormat _format; + + /// + /// Instantiates a new instance. + /// + public LanguageViewLocationExpander() + : this(LanguageViewLocationExpanderFormat.Suffix) + { + } + + /// + /// Instantiates a new instance. + /// + /// The . + public LanguageViewLocationExpander(LanguageViewLocationExpanderFormat format) + { + _format = format; + } + + /// + public void PopulateValues(ViewLocationExpanderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // Using CurrentUICulture so it loads the locale specific resources for the views. + context.Values[ValueKey] = CultureInfo.CurrentUICulture.Name; + } + + /// + public virtual IEnumerable ExpandViewLocations( + ViewLocationExpanderContext context, + IEnumerable viewLocations) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (viewLocations == null) + { + throw new ArgumentNullException(nameof(viewLocations)); + } + + context.Values.TryGetValue(ValueKey, out var value); + + if (!string.IsNullOrEmpty(value)) + { + CultureInfo culture; + try + { + culture = new CultureInfo(value); + } + catch (CultureNotFoundException) + { + return viewLocations; + } + + return ExpandViewLocationsCore(viewLocations, culture); + } + + return viewLocations; + } + + private IEnumerable ExpandViewLocationsCore(IEnumerable viewLocations, CultureInfo cultureInfo) + { + foreach (var location in viewLocations) + { + var temporaryCultureInfo = cultureInfo; + + while (temporaryCultureInfo != temporaryCultureInfo.Parent) + { + if (_format == LanguageViewLocationExpanderFormat.SubFolder) + { + yield return location.Replace("{0}", temporaryCultureInfo.Name + "/{0}"); + } + else + { + yield return location.Replace("{0}", "{0}." + temporaryCultureInfo.Name); + } + + temporaryCultureInfo = temporaryCultureInfo.Parent; + } + + yield return location; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpanderFormat.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpanderFormat.cs new file mode 100644 index 0000000000..ad4b177bb0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpanderFormat.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.Razor +{ + /// + /// Specifies the localized view format for . + /// + public enum LanguageViewLocationExpanderFormat + { + /// + /// Locale is a subfolder under which the view exists. + /// + /// + /// Home/Views/en-US/Index.chtml + /// + SubFolder, + + /// + /// Locale is part of the view name as a suffix. + /// + /// + /// Home/Views/Index.en-US.chtml + /// + Suffix + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj new file mode 100644 index 0000000000..74e943a8d9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj @@ -0,0 +1,60 @@ + + + + ASP.NET Core MVC Razor view engine for CSHTML files. + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;aspnetcoremvc;cshtml;razor + $(MSBuildProjectName).nuspec + true + + + + + + + + + + + + + + + + + + + + + + true + + id=$(MSBuildProjectName); + version=$(PackageVersion); + authors=$(Authors); + licenseUrl=$(PackageLicenseUrl); + projectUrl=$(PackageProjectUrl); + iconUrl=$(PackageIconUrl); + copyright=$(Copyright); + description=$(Description); + tags=$([MSBuild]::Escape($(PackageTags))); + serviceable=$(Serviceable); + repositoryUrl=$(RepositoryUrl); + repositoryCommit=$(RepositoryCommit); + targetframework=$(TargetFramework); + MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion=$(MicrosoftAspNetCoreMvcRazorExtensionsPackageVersion); + MicrosoftAspNetCoreRazorRuntimePackageVersion=$(MicrosoftAspNetCoreRazorRuntimePackageVersion); + MicrosoftCodeAnalysisRazorPackageVersion=$(MicrosoftCodeAnalysisRazorPackageVersion); + MicrosoftCodeAnalysisCSharpPackageVersion=$(MicrosoftCodeAnalysisCSharpPackageVersion); + MicrosoftExtensionsCachingMemoryPackageVersion=$(MicrosoftExtensionsCachingMemoryPackageVersion); + MicrosoftExtensionsFileProvidersCompositePackageVersion=$(MicrosoftExtensionsFileProvidersCompositePackageVersion); + MicrosoftDiaSymReaderNativePackageVersion=$(MicrosoftDiaSymReaderNativePackageVersion); + OutputBinary=@(BuiltProjectOutputGroupOutput); + OutputDocumentation=@(DocumentationProjectOutputGroupOutput); + OutputSymbol=@(DebugSymbolsProjectOutputGroupOutput); + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.nuspec b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.nuspec new file mode 100644 index 0000000000..9ff2439b96 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.nuspec @@ -0,0 +1,44 @@ + + + + $id$ + $version$ + $authors$ + true + $licenseUrl$ + $projectUrl$ + $iconUrl$ + $description$ + $copyright$ + $tags$ + $serviceable$ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..831fbe57e1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs @@ -0,0 +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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..3b6a518dd3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs @@ -0,0 +1,412 @@ +// +namespace Microsoft.AspNetCore.Mvc.Razor +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Mvc.Razor.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// Value cannot be null or empty. + /// + internal static string ArgumentCannotBeNullOrEmpty + { + get => GetString("ArgumentCannotBeNullOrEmpty"); + } + + /// + /// Value cannot be null or empty. + /// + internal static string FormatArgumentCannotBeNullOrEmpty() + => GetString("ArgumentCannotBeNullOrEmpty"); + + /// + /// One or more compilation failures occurred: + /// + internal static string CompilationFailed + { + get => GetString("CompilationFailed"); + } + + /// + /// One or more compilation failures occurred: + /// + internal static string FormatCompilationFailed() + => GetString("CompilationFailed"); + + /// + /// '{0}' cannot be invoked when a Layout page is set to be executed. + /// + internal static string FlushPointCannotBeInvoked + { + get => GetString("FlushPointCannotBeInvoked"); + } + + /// + /// '{0}' cannot be invoked when a Layout page is set to be executed. + /// + internal static string FormatFlushPointCannotBeInvoked(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("FlushPointCannotBeInvoked"), p0); + + /// + /// The layout view '{0}' could not be located. The following locations were searched:{1} + /// + internal static string LayoutCannotBeLocated + { + get => GetString("LayoutCannotBeLocated"); + } + + /// + /// The layout view '{0}' could not be located. The following locations were searched:{1} + /// + internal static string FormatLayoutCannotBeLocated(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("LayoutCannotBeLocated"), p0, p1); + + /// + /// Layout page '{0}' cannot be rendered after '{1}' has been invoked. + /// + internal static string LayoutCannotBeRendered + { + get => GetString("LayoutCannotBeRendered"); + } + + /// + /// Layout page '{0}' cannot be rendered after '{1}' has been invoked. + /// + internal static string FormatLayoutCannotBeRendered(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("LayoutCannotBeRendered"), p0, p1); + + /// + /// There is no active writing scope to end. + /// + internal static string RazorPage_ThereIsNoActiveWritingScopeToEnd + { + get => GetString("RazorPage_ThereIsNoActiveWritingScopeToEnd"); + } + + /// + /// There is no active writing scope to end. + /// + internal static string FormatRazorPage_ThereIsNoActiveWritingScopeToEnd() + => GetString("RazorPage_ThereIsNoActiveWritingScopeToEnd"); + + /// + /// The {0} operation cannot be performed while inside a writing scope in '{1}'. + /// + internal static string RazorPage_CannotFlushWhileInAWritingScope + { + get => GetString("RazorPage_CannotFlushWhileInAWritingScope"); + } + + /// + /// The {0} operation cannot be performed while inside a writing scope in '{1}'. + /// + internal static string FormatRazorPage_CannotFlushWhileInAWritingScope(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("RazorPage_CannotFlushWhileInAWritingScope"), p0, p1); + + /// + /// {0} invocation in '{1}' is invalid. {0} can only be called from a layout page. + /// + internal static string RazorPage_MethodCannotBeCalled + { + get => GetString("RazorPage_MethodCannotBeCalled"); + } + + /// + /// {0} invocation in '{1}' is invalid. {0} can only be called from a layout page. + /// + internal static string FormatRazorPage_MethodCannotBeCalled(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("RazorPage_MethodCannotBeCalled"), p0, p1); + + /// + /// {0} has not been called for the page at '{1}'. To ignore call {2}(). + /// + internal static string RenderBodyNotCalled + { + get => GetString("RenderBodyNotCalled"); + } + + /// + /// {0} has not been called for the page at '{1}'. To ignore call {2}(). + /// + internal static string FormatRenderBodyNotCalled(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("RenderBodyNotCalled"), p0, p1, p2); + + /// + /// Section '{0}' is already defined. + /// + internal static string SectionAlreadyDefined + { + get => GetString("SectionAlreadyDefined"); + } + + /// + /// Section '{0}' is already defined. + /// + internal static string FormatSectionAlreadyDefined(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("SectionAlreadyDefined"), p0); + + /// + /// {0} invocation in '{1}' is invalid. The section '{2}' has already been rendered. + /// + internal static string SectionAlreadyRendered + { + get => GetString("SectionAlreadyRendered"); + } + + /// + /// {0} invocation in '{1}' is invalid. The section '{2}' has already been rendered. + /// + internal static string FormatSectionAlreadyRendered(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("SectionAlreadyRendered"), p0, p1, p2); + + /// + /// The layout page '{0}' cannot find the section '{1}' in the content page '{2}'. + /// + internal static string SectionNotDefined + { + get => GetString("SectionNotDefined"); + } + + /// + /// The layout page '{0}' cannot find the section '{1}' in the content page '{2}'. + /// + internal static string FormatSectionNotDefined(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("SectionNotDefined"), p0, p1, p2); + + /// + /// The following sections have been defined but have not been rendered by the page at '{0}': '{1}'. To ignore an unrendered section call {2}("sectionName"). + /// + internal static string SectionsNotRendered + { + get => GetString("SectionsNotRendered"); + } + + /// + /// The following sections have been defined but have not been rendered by the page at '{0}': '{1}'. To ignore an unrendered section call {2}("sectionName"). + /// + internal static string FormatSectionsNotRendered(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("SectionsNotRendered"), p0, p1, p2); + + /// + /// '{0} must be set to access '{1}'. + /// + internal static string ViewContextMustBeSet + { + get => GetString("ViewContextMustBeSet"); + } + + /// + /// '{0} must be set to access '{1}'. + /// + internal static string FormatViewContextMustBeSet(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ViewContextMustBeSet"), p0, p1); + + /// + /// Generated Code + /// + internal static string GeneratedCodeFileName + { + get => GetString("GeneratedCodeFileName"); + } + + /// + /// Generated Code + /// + internal static string FormatGeneratedCodeFileName() + => GetString("GeneratedCodeFileName"); + + /// + /// Unable to perform '{0}' assignment. Tag helper property '{1}.{2}' must not be null. + /// + internal static string RazorPage_InvalidTagHelperIndexerAssignment + { + get => GetString("RazorPage_InvalidTagHelperIndexerAssignment"); + } + + /// + /// Unable to perform '{0}' assignment. Tag helper property '{1}.{2}' must not be null. + /// + internal static string FormatRazorPage_InvalidTagHelperIndexerAssignment(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("RazorPage_InvalidTagHelperIndexerAssignment"), p0, p1, p2); + + /// + /// Unexpected return value from '{1}.{2}' for URL '{0}'. If the '{1}' service has been overridden, change '{2}' to replace only the '~/' prefix. Otherwise, add the following directive to the Razor page to disable URL resolution relative to the application's 'webroot' setting: + /// + /// @{3} "{4}, {5}" + /// + internal static string CouldNotResolveApplicationRelativeUrl_TagHelper + { + get => GetString("CouldNotResolveApplicationRelativeUrl_TagHelper"); + } + + /// + /// Unexpected return value from '{1}.{2}' for URL '{0}'. If the '{1}' service has been overridden, change '{2}' to replace only the '~/' prefix. Otherwise, add the following directive to the Razor page to disable URL resolution relative to the application's 'webroot' setting: + /// + /// @{3} "{4}, {5}" + /// + internal static string FormatCouldNotResolveApplicationRelativeUrl_TagHelper(object p0, object p1, object p2, object p3, object p4, object p5) + => string.Format(CultureInfo.CurrentCulture, GetString("CouldNotResolveApplicationRelativeUrl_TagHelper"), p0, p1, p2, p3, p4, p5); + + /// + /// A circular layout reference was detected when rendering '{0}'. The layout page '{1}' has already been rendered. + /// + internal static string LayoutHasCircularReference + { + get => GetString("LayoutHasCircularReference"); + } + + /// + /// A circular layout reference was detected when rendering '{0}'. The layout page '{1}' has already been rendered. + /// + internal static string FormatLayoutHasCircularReference(object p0, object p1) + => 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. + /// + internal static string Compilation_DependencyContextIsNotSpecified + { + get => GetString("Compilation_DependencyContextIsNotSpecified"); + } + + /// + /// One or more compilation references are missing. Ensure that your project is referencing '{0}' and the '{1}' property is not set to false. + /// + internal static string FormatCompilation_DependencyContextIsNotSpecified(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("Compilation_DependencyContextIsNotSpecified"), p0, p1); + + /// + /// '{0}' cannot be empty. These locations are required to locate a view for rendering. + /// + internal static string ViewLocationFormatsIsRequired + { + get => GetString("ViewLocationFormatsIsRequired"); + } + + /// + /// '{0}' cannot be empty. These locations are required to locate a view for rendering. + /// + internal static string FormatViewLocationFormatsIsRequired(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ViewLocationFormatsIsRequired"), p0); + + /// + /// Nesting of TagHelper attribute writing scopes is not supported. + /// + internal static string RazorPage_NestingAttributeWritingScopesNotSupported + { + get => GetString("RazorPage_NestingAttributeWritingScopesNotSupported"); + } + + /// + /// Nesting of TagHelper attribute writing scopes is not supported. + /// + internal static string FormatRazorPage_NestingAttributeWritingScopesNotSupported() + => GetString("RazorPage_NestingAttributeWritingScopesNotSupported"); + + /// + /// '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering. + /// + internal static string FileProvidersAreRequired + { + get => GetString("FileProvidersAreRequired"); + } + + /// + /// '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering. + /// + internal static string FormatFileProvidersAreRequired(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("FileProvidersAreRequired"), p0, p1, p2); + + /// + /// Path must begin with a forward slash '/'. + /// + internal static string RazorProject_PathMustStartWithForwardSlash + { + get => GetString("RazorProject_PathMustStartWithForwardSlash"); + } + + /// + /// Path must begin with a forward slash '/'. + /// + internal static string FormatRazorProject_PathMustStartWithForwardSlash() + => GetString("RazorProject_PathMustStartWithForwardSlash"); + + /// + /// The property '{0}' of '{1}' must not be null. + /// + internal static string PropertyMustBeSet + { + get => GetString("PropertyMustBeSet"); + } + + /// + /// The property '{0}' of '{1}' must not be null. + /// + internal static string FormatPropertyMustBeSet(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("PropertyMustBeSet"), p0, p1); + + /// + /// The following precompiled view paths differ only in case, which is not supported: + /// + internal static string RazorViewCompiler_ViewPathsDifferOnlyInCase + { + get => GetString("RazorViewCompiler_ViewPathsDifferOnlyInCase"); + } + + /// + /// The following precompiled view paths differ only in case, which is not supported: + /// + internal static string FormatRazorViewCompiler_ViewPathsDifferOnlyInCase() + => GetString("RazorViewCompiler_ViewPathsDifferOnlyInCase"); + + /// + /// The debug type specified in the dependency context could be parsed. The debug type value '{0}' is not supported. + /// + internal static string UnsupportedDebugInformationFormat + { + get => GetString("UnsupportedDebugInformationFormat"); + } + + /// + /// The debug type specified in the dependency context could be parsed. The debug type value '{0}' is not supported. + /// + internal static string FormatUnsupportedDebugInformationFormat(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedDebugInformationFormat"), p0); + + /// + /// At least one of the '{0}' or '{1}' values must be non-null. + /// + internal static string CompiledViewDescriptor_NoData + { + get => GetString("CompiledViewDescriptor_NoData"); + } + + /// + /// At least one of the '{0}' or '{1}' values must be non-null. + /// + internal static string FormatCompiledViewDescriptor_NoData(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("CompiledViewDescriptor_NoData"), 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/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs new file mode 100644 index 0000000000..3d13f977f7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs @@ -0,0 +1,319 @@ +// Copyright (c) .NET 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.Html; +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Represents properties and methods that are needed in order to render a view that uses Razor syntax. + /// + public abstract class RazorPage : RazorPageBase + { + private readonly HashSet _renderedSections = new HashSet(StringComparer.OrdinalIgnoreCase); + private bool _renderedBody; + private bool _ignoreBody; + private HashSet _ignoredSections; + + /// + /// An representing the current request execution. + /// + public HttpContext Context => ViewContext?.HttpContext; + + /// + /// In a Razor layout page, renders the portion of a content page that is not within a named section. + /// + /// The HTML content to render. + protected virtual IHtmlContent RenderBody() + { + if (BodyContent == null) + { + var message = Resources.FormatRazorPage_MethodCannotBeCalled(nameof(RenderBody), Path); + throw new InvalidOperationException(message); + } + + _renderedBody = true; + return BodyContent; + } + + /// + /// In a Razor layout page, ignores rendering the portion of a content page that is not within a named section. + /// + public void IgnoreBody() + { + _ignoreBody = true; + } + + /// + /// Creates a named content section in the page that can be invoked in a Layout page using + /// or . + /// + /// The name of the section to create. + /// The to execute when rendering the section. + public override void DefineSection(string name, RenderAsyncDelegate section) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (section == null) + { + throw new ArgumentNullException(nameof(section)); + } + + if (SectionWriters.ContainsKey(name)) + { + throw new InvalidOperationException(Resources.FormatSectionAlreadyDefined(name)); + } + SectionWriters[name] = section; + } + + /// + /// Returns a value that indicates whether the specified section is defined in the content page. + /// + /// The section name to search for. + /// true if the specified section is defined in the content page; otherwise, false. + public bool IsSectionDefined(string name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + EnsureMethodCanBeInvoked(nameof(IsSectionDefined)); + return PreviousSectionWriters.ContainsKey(name); + } + + /// + /// In layout pages, renders the content of the section named . + /// + /// The name of the section to render. + /// An empty . + /// The method writes to the and the value returned is a token + /// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the + /// value does not represent the rendered content. + public HtmlString RenderSection(string name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return RenderSection(name, required: true); + } + + /// + /// In layout pages, renders the content of the section named . + /// + /// The section to render. + /// Indicates if this section must be rendered. + /// An empty . + /// The method writes to the and the value returned is a token + /// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the + /// value does not represent the rendered content. + public HtmlString RenderSection(string name, bool required) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + EnsureMethodCanBeInvoked(nameof(RenderSection)); + + var task = RenderSectionAsyncCore(name, required); + return task.GetAwaiter().GetResult(); + } + + /// + /// In layout pages, asynchronously renders the content of the section named . + /// + /// The section to render. + /// + /// A that on completion returns an empty . + /// + /// The method writes to the and the value returned is a token + /// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the + /// value does not represent the rendered content. + public Task RenderSectionAsync(string name) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + return RenderSectionAsync(name, required: true); + } + + /// + /// In layout pages, asynchronously renders the content of the section named . + /// + /// The section to render. + /// Indicates the section must be registered + /// (using @section) in the page. + /// + /// A that on completion returns an empty . + /// + /// The method writes to the and the value returned is a token + /// value that allows the Write (produced due to @RenderSection(..)) to succeed. However the + /// value does not represent the rendered content. + /// if is true and the section + /// was not registered using the @section in the Razor page. + public Task RenderSectionAsync(string name, bool required) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + EnsureMethodCanBeInvoked(nameof(RenderSectionAsync)); + return RenderSectionAsyncCore(name, required); + } + + private async Task RenderSectionAsyncCore(string sectionName, bool required) + { + if (_renderedSections.Contains(sectionName)) + { + var message = Resources.FormatSectionAlreadyRendered(nameof(RenderSectionAsync), Path, sectionName); + throw new InvalidOperationException(message); + } + + if (PreviousSectionWriters.TryGetValue(sectionName, out var renderDelegate)) + { + _renderedSections.Add(sectionName); + + await renderDelegate(); + + // Return a token value that allows the Write call that wraps the RenderSection \ RenderSectionAsync + // to succeed. + return HtmlString.Empty; + } + 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); + } + else + { + // If the section is optional and not found, then don't do anything. + return null; + } + } + + /// + /// In layout pages, ignores rendering the content of the section named . + /// + /// The section to ignore. + public void IgnoreSection(string sectionName) + { + if (sectionName == null) + { + throw new ArgumentNullException(nameof(sectionName)); + } + + if (!PreviousSectionWriters.ContainsKey(sectionName)) + { + // If the section is not defined, throw an error. + throw new InvalidOperationException(Resources.FormatSectionNotDefined( + ViewContext.ExecutingFilePath, + sectionName, + ViewContext.View.Path)); + } + + if (_ignoredSections == null) + { + _ignoredSections = new HashSet(StringComparer.OrdinalIgnoreCase); + } + + _ignoredSections.Add(sectionName); + } + + /// + public override void EnsureRenderedBodyOrSections() + { + // a) all sections defined for this page are rendered. + // b) if no sections are defined, then the body is rendered if it's available. + if (PreviousSectionWriters != null && PreviousSectionWriters.Count > 0) + { + var sectionsNotRendered = PreviousSectionWriters.Keys.Except( + _renderedSections, + StringComparer.OrdinalIgnoreCase); + + string[] sectionsNotIgnored; + if (_ignoredSections != null) + { + sectionsNotIgnored = sectionsNotRendered.Except(_ignoredSections, StringComparer.OrdinalIgnoreCase).ToArray(); + } + else + { + sectionsNotIgnored = sectionsNotRendered.ToArray(); + } + + if (sectionsNotIgnored.Length > 0) + { + var sectionNames = string.Join(", ", sectionsNotIgnored); + throw new InvalidOperationException(Resources.FormatSectionsNotRendered(Path, sectionNames, nameof(IgnoreSection))); + } + } + else if (BodyContent != null && !_renderedBody && !_ignoreBody) + { + // There are no sections defined, but RenderBody was NOT called. + // If a body was defined and the body not ignored, then RenderBody should have been called. + var message = Resources.FormatRenderBodyNotCalled(nameof(RenderBody), Path, nameof(IgnoreBody)); + throw new InvalidOperationException(message); + } + } + + public override void BeginContext(int position, int length, bool isLiteral) + { + const string BeginContextEvent = "Microsoft.AspNetCore.Mvc.Razor.BeginInstrumentationContext"; + + if (DiagnosticSource?.IsEnabled(BeginContextEvent) == true) + { + DiagnosticSource.Write( + BeginContextEvent, + new + { + httpContext = Context, + path = Path, + position = position, + length = length, + isLiteral = isLiteral, + }); + } + } + + public override void EndContext() + { + const string EndContextEvent = "Microsoft.AspNetCore.Mvc.Razor.EndInstrumentationContext"; + + if (DiagnosticSource?.IsEnabled(EndContextEvent) == true) + { + DiagnosticSource.Write( + EndContextEvent, + new + { + httpContext = Context, + path = Path, + }); + } + } + + private void EnsureMethodCanBeInvoked(string methodName) + { + if (PreviousSectionWriters == null) + { + throw new InvalidOperationException(Resources.FormatRazorPage_MethodCannotBeCalled(methodName, Path)); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageActivator.cs new file mode 100644 index 0000000000..758255f494 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageActivator.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; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Reflection; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + public class RazorPageActivator : IRazorPageActivator + { + // Name of the "public TModel Model" property on RazorPage + private const string ModelPropertyName = "Model"; + private readonly ConcurrentDictionary _activationInfo; + private readonly IModelMetadataProvider _metadataProvider; + + // Value accessors for common singleton properties activated in a RazorPage. + private readonly RazorPagePropertyActivator.PropertyValueAccessors _propertyAccessors; + + /// + /// Initializes a new instance of the class. + /// + public RazorPageActivator( + IModelMetadataProvider metadataProvider, + IUrlHelperFactory urlHelperFactory, + IJsonHelper jsonHelper, + DiagnosticSource diagnosticSource, + HtmlEncoder htmlEncoder, + IModelExpressionProvider modelExpressionProvider) + { + _activationInfo = new ConcurrentDictionary(); + _metadataProvider = metadataProvider; + + _propertyAccessors = new RazorPagePropertyActivator.PropertyValueAccessors + { + UrlHelperAccessor = context => urlHelperFactory.GetUrlHelper(context), + JsonHelperAccessor = context => jsonHelper, + DiagnosticSourceAccessor = context => diagnosticSource, + HtmlEncoderAccessor = context => htmlEncoder, + ModelExpressionProviderAccessor = context => modelExpressionProvider, + }; + } + + /// + public void Activate(IRazorPage page, ViewContext context) + { + if (page == null) + { + throw new ArgumentNullException(nameof(page)); + } + + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var pageType = page.GetType(); + RazorPagePropertyActivator propertyActivator; + if (!_activationInfo.TryGetValue(pageType, out 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; + propertyActivator = new RazorPagePropertyActivator( + pageType, + modelType, + _metadataProvider, + _propertyAccessors); + + propertyActivator = _activationInfo.GetOrAdd(pageType, propertyActivator); + } + + propertyActivator.Activate(page, context); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs new file mode 100644 index 0000000000..96ef957f60 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs @@ -0,0 +1,774 @@ +// Copyright (c) .NET 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.Diagnostics; +using System.IO; +using System.Security.Claims; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Antiforgery; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; +using Microsoft.AspNetCore.Razor.Runtime.TagHelpers; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Represents properties and methods that are needed in order to render a view that uses Razor syntax. + /// + public abstract class RazorPageBase : IRazorPage + { + private readonly Stack _textWriterStack = new Stack(); + private StringWriter _valueBuffer; + private ITagHelperFactory _tagHelperFactory; + private IViewBufferScope _bufferScope; + private TextWriter _pageWriter; + private AttributeInfo _attributeInfo; + private TagHelperAttributeInfo _tagHelperAttributeInfo; + private IUrlHelper _urlHelper; + + public virtual ViewContext ViewContext { get; set; } + + public string Layout { get; set; } + + /// + /// Gets the that the page is writing output to. + /// + /// + /// Gets the that the page is writing output to. + /// + public virtual TextWriter Output + { + get + { + if (ViewContext == null) + { + var message = Resources.FormatViewContextMustBeSet("ViewContext", "Output"); + throw new InvalidOperationException(message); + } + + return ViewContext.Writer; + } + } + + /// + public string Path { get; set; } + + /// + public IDictionary SectionWriters { get; } = + new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Gets the dynamic view data dictionary. + /// + public dynamic ViewBag => ViewContext?.ViewBag; + + /// + public bool IsLayoutBeingRendered { get; set; } + + /// + public IHtmlContent BodyContent { get; set; } + + /// + public IDictionary PreviousSectionWriters { get; set; } + + /// + /// Gets or sets a instance used to instrument the page execution. + /// + [RazorInject] + public DiagnosticSource DiagnosticSource { get; set; } + + /// + /// Gets the to use when this + /// handles non- C# expressions. + /// + [RazorInject] + public HtmlEncoder HtmlEncoder { get; set; } + + /// + /// Gets the of the current logged in user. + /// + public virtual ClaimsPrincipal User => ViewContext?.HttpContext?.User; + + /// + /// Gets the from the . + /// + /// Returns null if is null. + public ITempDataDictionary TempData => ViewContext?.TempData; + + private Stack TagHelperScopes { get; } = new Stack(); + + private ITagHelperFactory TagHelperFactory + { + get + { + if (_tagHelperFactory == null) + { + var services = ViewContext.HttpContext.RequestServices; + _tagHelperFactory = services.GetRequiredService(); + } + + return _tagHelperFactory; + } + } + + private IViewBufferScope BufferScope + { + get + { + if (_bufferScope == null) + { + var services = ViewContext.HttpContext.RequestServices; + _bufferScope = services.GetRequiredService(); + } + + return _bufferScope; + } + } + + public abstract Task ExecuteAsync(); + + /// + /// Format an error message about using an indexer when the tag helper property is null. + /// + /// Name of the HTML attribute associated with the indexer. + /// Full name of the tag helper . + /// Dictionary property in the tag helper. + /// An error message about using an indexer when the tag helper property is null. + [EditorBrowsable(EditorBrowsableState.Never)] + public string InvalidTagHelperIndexerAssignment( + string attributeName, + string tagHelperTypeName, + string propertyName) + { + return Resources.FormatRazorPage_InvalidTagHelperIndexerAssignment( + attributeName, + tagHelperTypeName, + propertyName); + } + + /// + /// Creates and activates a . + /// + /// A type. + /// The activated . + /// + /// must have a parameterless constructor. + /// + public TTagHelper CreateTagHelper() where TTagHelper : ITagHelper + { + return TagHelperFactory.CreateTagHelper(ViewContext); + } + + /// + /// Starts a new writing scope and optionally overrides within that scope. + /// + /// + /// The to use when this handles + /// non- C# expressions. If null, does not change . + /// + /// + /// All writes to the or after calling this method will + /// be buffered until is called. + /// + public void StartTagHelperWritingScope(HtmlEncoder encoder) + { + var buffer = new ViewBuffer(BufferScope, Path, ViewBuffer.TagHelperPageSize); + TagHelperScopes.Push(new TagHelperScopeInfo(buffer, HtmlEncoder, ViewContext.Writer)); + + // If passed an HtmlEncoder, override the property. + if (encoder != null) + { + HtmlEncoder = encoder; + } + + // 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); + } + + /// + /// Ends the current writing scope that was started by calling . + /// + /// The buffered . + public TagHelperContent EndTagHelperWritingScope() + { + if (TagHelperScopes.Count == 0) + { + throw new InvalidOperationException(Resources.RazorPage_ThereIsNoActiveWritingScopeToEnd); + } + + var scopeInfo = TagHelperScopes.Pop(); + + // Get the content written during the current scope. + var tagHelperContent = new DefaultTagHelperContent(); + tagHelperContent.AppendHtml(scopeInfo.Buffer); + + // Restore previous scope. + HtmlEncoder = scopeInfo.HtmlEncoder; + ViewContext.Writer = scopeInfo.Writer; + + return tagHelperContent; + } + + /// + /// Starts a new scope for writing attribute values. + /// + /// + /// All writes to the or after calling this method will + /// be buffered until is called. + /// The content will be buffered using a shared within this + /// Nesting of and method calls + /// is not supported. + /// + public void BeginWriteTagHelperAttribute() + { + if (_pageWriter != null) + { + throw new InvalidOperationException(Resources.RazorPage_NestingAttributeWritingScopesNotSupported); + } + + _pageWriter = ViewContext.Writer; + + if (_valueBuffer == null) + { + _valueBuffer = new StringWriter(); + } + + // We need to replace the ViewContext's Writer to ensure that all content (including content written + // from HTML helpers) is redirected. + ViewContext.Writer = _valueBuffer; + + } + + /// + /// Ends the current writing scope that was started by calling . + /// + /// The content buffered by the shared of this . + /// + /// This method assumes that there will be no nesting of + /// and method calls. + /// + public string EndWriteTagHelperAttribute() + { + if (_pageWriter == null) + { + throw new InvalidOperationException(Resources.RazorPage_ThereIsNoActiveWritingScopeToEnd); + } + + var content = _valueBuffer.ToString(); + _valueBuffer.GetStringBuilder().Clear(); + + // Restore previous writer. + ViewContext.Writer = _pageWriter; + _pageWriter = null; + + return content; + } + + // Internal for unit testing. + protected internal virtual void PushWriter(TextWriter writer) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + _textWriterStack.Push(ViewContext.Writer); + ViewContext.Writer = writer; + } + + // Internal for unit testing. + protected internal virtual TextWriter PopWriter() + { + ViewContext.Writer = _textWriterStack.Pop(); + return ViewContext.Writer; + } + + public virtual string Href(string contentPath) + { + if (contentPath == null) + { + throw new ArgumentNullException(nameof(contentPath)); + } + + if (_urlHelper == null) + { + var services = ViewContext?.HttpContext.RequestServices; + var factory = services.GetRequiredService(); + _urlHelper = factory.GetUrlHelper(ViewContext); + } + + return _urlHelper.Content(contentPath); + } + + /// + /// Creates a named content section in the page that can be invoked in a Layout page using + /// RenderSection or RenderSectionAsync + /// + /// The name of the section to create. + /// The delegate to execute when rendering the section. + /// This is a temporary placeholder method to support ASP.NET Core 2.0.0 editor code generation. + [EditorBrowsable(EditorBrowsableState.Never)] + protected void DefineSection(string name, Func section) + => DefineSection(name, () => section(null /* writer */)); + + /// + /// Creates a named content section in the page that can be invoked in a Layout page using + /// RenderSection or RenderSectionAsync + /// + /// The name of the section to create. + /// The to execute when rendering the section. + public virtual void DefineSection(string name, RenderAsyncDelegate section) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (section == null) + { + throw new ArgumentNullException(nameof(section)); + } + + if (SectionWriters.ContainsKey(name)) + { + throw new InvalidOperationException(Resources.FormatSectionAlreadyDefined(name)); + } + SectionWriters[name] = section; + } + + + /// + /// Writes the specified with HTML encoding to . + /// + /// The to write. + public virtual void Write(object value) + { + if (value == null || value == HtmlString.Empty) + { + return; + } + + var writer = Output; + var encoder = HtmlEncoder; + if (value is IHtmlContent htmlContent) + { + var bufferedWriter = writer as ViewBufferTextWriter; + if (bufferedWriter == null || !bufferedWriter.IsBuffering) + { + htmlContent.WriteTo(writer, encoder); + } + else + { + if (value is IHtmlContentContainer htmlContentContainer) + { + // This is likely another ViewBuffer. + htmlContentContainer.MoveTo(bufferedWriter.Buffer); + } + else + { + // Perf: This is the common case for IHtmlContent, ViewBufferTextWriter is inefficient + // for writing character by character. + bufferedWriter.Buffer.AppendHtml(htmlContent); + } + } + + return; + } + + Write(value.ToString()); + } + + /// + /// Writes the specified with HTML encoding to . + /// + /// The to write. + public virtual void Write(string value) + { + var writer = Output; + var encoder = HtmlEncoder; + if (!string.IsNullOrEmpty(value)) + { + // Perf: Encode right away instead of writing it character-by-character. + // character-by-character isn't efficient when using a writer backed by a ViewBuffer. + var encoded = encoder.Encode(value); + writer.Write(encoded); + } + } + + /// + /// Writes the specified without HTML encoding to . + /// + /// The to write. + public virtual void WriteLiteral(object value) + { + if (value == null) + { + return; + } + + WriteLiteral(value.ToString()); + } + + /// + /// Writes the specified without HTML encoding to . + /// + /// The to write. + public virtual void WriteLiteral(string value) + { + if (!string.IsNullOrEmpty(value)) + { + Output.Write(value); + } + } + + public virtual void BeginWriteAttribute( + string name, + string prefix, + int prefixOffset, + string suffix, + int suffixOffset, + int attributeValuesCount) + { + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + if (suffix == null) + { + throw new ArgumentNullException(nameof(suffix)); + } + + _attributeInfo = new AttributeInfo(name, prefix, prefixOffset, suffix, suffixOffset, attributeValuesCount); + + // Single valued attributes might be omitted in entirety if it the attribute value strictly evaluates to + // null or false. Consequently defer the prefix generation until we encounter the attribute value. + if (attributeValuesCount != 1) + { + WritePositionTaggedLiteral(prefix, prefixOffset); + } + } + + public void WriteAttributeValue( + string prefix, + int prefixOffset, + object value, + int valueOffset, + int valueLength, + bool isLiteral) + { + if (_attributeInfo.AttributeValuesCount == 1) + { + if (IsBoolFalseOrNullValue(prefix, value)) + { + // Value is either null or the bool 'false' with no prefix; don't render the attribute. + _attributeInfo.Suppressed = true; + return; + } + + // We are not omitting the attribute. Write the prefix. + WritePositionTaggedLiteral(_attributeInfo.Prefix, _attributeInfo.PrefixOffset); + + if (IsBoolTrueWithEmptyPrefixValue(prefix, value)) + { + // The value is just the bool 'true', write the attribute name instead of the string 'True'. + value = _attributeInfo.Name; + } + } + + // This block handles two cases. + // 1. Single value with prefix. + // 2. Multiple values with or without prefix. + if (value != null) + { + if (!string.IsNullOrEmpty(prefix)) + { + WritePositionTaggedLiteral(prefix, prefixOffset); + } + + BeginContext(valueOffset, valueLength, isLiteral); + + WriteUnprefixedAttributeValue(value, isLiteral); + + EndContext(); + } + } + + public virtual void EndWriteAttribute() + { + if (!_attributeInfo.Suppressed) + { + WritePositionTaggedLiteral(_attributeInfo.Suffix, _attributeInfo.SuffixOffset); + } + } + + public void BeginAddHtmlAttributeValues( + TagHelperExecutionContext executionContext, + string attributeName, + int attributeValuesCount, + HtmlAttributeValueStyle attributeValueStyle) + { + _tagHelperAttributeInfo = new TagHelperAttributeInfo( + executionContext, + attributeName, + attributeValuesCount, + attributeValueStyle); + } + + public void AddHtmlAttributeValue( + string prefix, + int prefixOffset, + object value, + int valueOffset, + int valueLength, + bool isLiteral) + { + Debug.Assert(_tagHelperAttributeInfo.ExecutionContext != null); + if (_tagHelperAttributeInfo.AttributeValuesCount == 1) + { + if (IsBoolFalseOrNullValue(prefix, value)) + { + // The first value was 'null' or 'false' indicating that we shouldn't render the attribute. The + // attribute is treated as a TagHelper attribute so it's only available in + // TagHelperContext.AllAttributes for TagHelper authors to see (if they want to see why the + // attribute was removed from TagHelperOutput.Attributes). + _tagHelperAttributeInfo.ExecutionContext.AddTagHelperAttribute( + _tagHelperAttributeInfo.Name, + value?.ToString() ?? string.Empty, + _tagHelperAttributeInfo.AttributeValueStyle); + _tagHelperAttributeInfo.Suppressed = true; + return; + } + else if (IsBoolTrueWithEmptyPrefixValue(prefix, value)) + { + _tagHelperAttributeInfo.ExecutionContext.AddHtmlAttribute( + _tagHelperAttributeInfo.Name, + _tagHelperAttributeInfo.Name, + _tagHelperAttributeInfo.AttributeValueStyle); + _tagHelperAttributeInfo.Suppressed = true; + return; + } + } + + if (value != null) + { + // Perf: We'll use this buffer for all of the attribute values and then clear it to + // reduce allocations. + if (_valueBuffer == null) + { + _valueBuffer = new StringWriter(); + } + + PushWriter(_valueBuffer); + if (!string.IsNullOrEmpty(prefix)) + { + WriteLiteral(prefix); + } + + WriteUnprefixedAttributeValue(value, isLiteral); + PopWriter(); + } + } + + public void EndAddHtmlAttributeValues(TagHelperExecutionContext executionContext) + { + if (!_tagHelperAttributeInfo.Suppressed) + { + // Perf: _valueBuffer might be null if nothing was written. If it is set, clear it so + // it is reset for the next value. + var content = _valueBuffer == null ? HtmlString.Empty : new HtmlString(_valueBuffer.ToString()); + _valueBuffer?.GetStringBuilder().Clear(); + + executionContext.AddHtmlAttribute(_tagHelperAttributeInfo.Name, content, _tagHelperAttributeInfo.AttributeValueStyle); + } + } + + /// + /// Invokes on and + /// on the response stream, writing out any buffered content to the . + /// + /// A that represents the asynchronous flush operation and on + /// completion returns an empty . + /// The value returned is a token value that allows FlushAsync to work directly in an HTML + /// section. However the value does not represent the rendered content. + /// This method also writes out headers, so any modifications to headers must be done before + /// is called. For example, call to send + /// antiforgery cookie token and X-Frame-Options header to client before this method flushes headers out. + /// + public virtual async Task FlushAsync() + { + // If there are active scopes, then we should throw. Cannot flush content that has the potential to change. + if (TagHelperScopes.Count > 0) + { + throw new InvalidOperationException( + Resources.FormatRazorPage_CannotFlushWhileInAWritingScope(nameof(FlushAsync), Path)); + } + + // Calls to Flush are allowed if the page does not specify a Layout or if it is executing a section in the + // Layout. + if (!IsLayoutBeingRendered && !string.IsNullOrEmpty(Layout)) + { + var message = Resources.FormatLayoutCannotBeRendered(Path, nameof(FlushAsync)); + throw new InvalidOperationException(message); + } + + await Output.FlushAsync(); + await ViewContext?.HttpContext.Response.Body.FlushAsync(); + return HtmlString.Empty; + } + + /// + /// Sets antiforgery cookie and X-Frame-Options header on the response. + /// + /// An empty . + /// Call this method to send antiforgery cookie token and X-Frame-Options header to client + /// before flushes the headers. + public virtual HtmlString SetAntiforgeryCookieAndHeader() + { + var antiforgery = ViewContext?.HttpContext.RequestServices.GetRequiredService(); + antiforgery.SetCookieTokenAndHeader(ViewContext?.HttpContext); + + return HtmlString.Empty; + } + + private void WriteUnprefixedAttributeValue(object value, bool isLiteral) + { + var stringValue = value as string; + + // The extra branching here is to ensure that we call the Write*To(string) overload where possible. + if (isLiteral && stringValue != null) + { + WriteLiteral(stringValue); + } + else if (isLiteral) + { + WriteLiteral(value); + } + else if (stringValue != null) + { + Write(stringValue); + } + else + { + Write(value); + } + } + + private void WritePositionTaggedLiteral(string value, int position) + { + BeginContext(position, value.Length, isLiteral: true); + WriteLiteral(value); + EndContext(); + } + + public abstract void BeginContext(int position, int length, bool isLiteral); + + public abstract void EndContext(); + + private bool IsBoolFalseOrNullValue(string prefix, object value) + { + return string.IsNullOrEmpty(prefix) && + (value == null || + (value is bool && !(bool)value)); + } + + private bool IsBoolTrueWithEmptyPrefixValue(string prefix, object value) + { + // If the value is just the bool 'true', use the attribute name as the value. + return string.IsNullOrEmpty(prefix) && + (value is bool && (bool)value); + } + + public abstract void EnsureRenderedBodyOrSections(); + + private struct AttributeInfo + { + public AttributeInfo( + string name, + string prefix, + int prefixOffset, + string suffix, + int suffixOffset, + int attributeValuesCount) + { + Name = name; + Prefix = prefix; + PrefixOffset = prefixOffset; + Suffix = suffix; + SuffixOffset = suffixOffset; + AttributeValuesCount = attributeValuesCount; + + Suppressed = false; + } + + public int AttributeValuesCount { get; } + + public string Name { get; } + + public string Prefix { get; } + + public int PrefixOffset { get; } + + public string Suffix { get; } + + public int SuffixOffset { get; } + + public bool Suppressed { get; set; } + } + + private struct TagHelperAttributeInfo + { + public TagHelperAttributeInfo( + TagHelperExecutionContext tagHelperExecutionContext, + string name, + int attributeValuesCount, + HtmlAttributeValueStyle attributeValueStyle) + { + ExecutionContext = tagHelperExecutionContext; + Name = name; + AttributeValuesCount = attributeValuesCount; + AttributeValueStyle = attributeValueStyle; + + Suppressed = false; + } + + public string Name { get; } + + public TagHelperExecutionContext ExecutionContext { get; } + + public int AttributeValuesCount { get; } + + public HtmlAttributeValueStyle AttributeValueStyle { get; } + + public bool Suppressed { get; set; } + } + + private struct TagHelperScopeInfo + { + public TagHelperScopeInfo(ViewBuffer buffer, HtmlEncoder encoder, TextWriter writer) + { + Buffer = buffer; + HtmlEncoder = encoder; + Writer = writer; + } + + public ViewBuffer Buffer { get; } + + public HtmlEncoder HtmlEncoder { get; } + + public TextWriter Writer { get; } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs new file mode 100644 index 0000000000..31f7b1ce94 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.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.AspNetCore.Mvc.Razor.Compilation; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Result of . + /// + public struct RazorPageFactoryResult + { + /// + /// Initializes a new instance of with the + /// specified factory. + /// + /// The factory. + /// The . + public RazorPageFactoryResult( + CompiledViewDescriptor viewDescriptor, + Func razorPageFactory) + { + ViewDescriptor = viewDescriptor ?? throw new ArgumentNullException(nameof(viewDescriptor)); + RazorPageFactory = razorPageFactory; + } + + /// + /// The factory. + /// + /// This property is null when is false. + public Func RazorPageFactory { get; } + + /// + /// Gets the . + /// + public CompiledViewDescriptor ViewDescriptor { get; } + + /// + /// Gets a value that determines if the page was successfully located. + /// + public bool Success => RazorPageFactory != null; + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageOfT.cs new file mode 100644 index 0000000000..923cf13525 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageOfT.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.Razor.Internal; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Represents the properties and methods that are needed in order to render a view that uses Razor syntax. + /// + /// The type of the view data model. + public abstract class RazorPage : RazorPage + { + /// + /// Gets the Model property of the property. + /// + public TModel Model => ViewData == null ? default(TModel) : ViewData.Model; + + /// + /// Gets or sets the dictionary for view data. + /// + [RazorInject] + public ViewDataDictionary ViewData { get; set; } + + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageResult.cs new file mode 100644 index 0000000000..c299e350fe --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageResult.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; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Result of locating a . + /// + public struct RazorPageResult + { + /// + /// Initializes a new instance of for a successful discovery. + /// + /// The name of the page that was found. + /// The located . + public RazorPageResult(string name, IRazorPage page) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (page == null) + { + throw new ArgumentNullException(nameof(page)); + } + + Name = name; + Page = page; + SearchedLocations = null; + } + + /// + /// Initializes a new instance of for an unsuccessful discovery. + /// + /// The name of the page that was not found. + /// The locations that were searched. + public RazorPageResult(string name, IEnumerable searchedLocations) + { + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (searchedLocations == null) + { + throw new ArgumentNullException(nameof(searchedLocations)); + } + + Name = name; + Page = null; + SearchedLocations = searchedLocations; + } + + /// + /// Gets the name or the path of the page being located. + /// + public string Name { get; } + + /// + /// Gets the if found. + /// + /// This property is null if the page was not found. + public IRazorPage Page { get; } + + /// + /// Gets the locations that were searched when could not be found. + /// + /// This property is null if the page was found. + public IEnumerable SearchedLocations { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorView.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorView.cs new file mode 100644 index 0000000000..240c80681c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorView.cs @@ -0,0 +1,327 @@ +// Copyright (c) .NET 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.Linq; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Default implementation for that executes one or more + /// as parts of its execution. + /// + public class RazorView : IView + { + private readonly IRazorViewEngine _viewEngine; + private readonly IRazorPageActivator _pageActivator; + private readonly HtmlEncoder _htmlEncoder; + private readonly DiagnosticSource _diagnosticSource; + private IViewBufferScope _bufferScope; + + /// + /// Initializes a new instance of + /// + /// The used to locate Layout pages. + /// The used to activate pages. + /// The sequence of instances executed as _ViewStarts. + /// + /// The instance to execute. + /// The HTML encoder. + /// The . + public RazorView( + IRazorViewEngine viewEngine, + IRazorPageActivator pageActivator, + IReadOnlyList viewStartPages, + IRazorPage razorPage, + HtmlEncoder htmlEncoder, + DiagnosticSource diagnosticSource) + { + if (viewEngine == null) + { + throw new ArgumentNullException(nameof(viewEngine)); + } + + if (pageActivator == null) + { + throw new ArgumentNullException(nameof(pageActivator)); + } + + if (viewStartPages == null) + { + throw new ArgumentNullException(nameof(viewStartPages)); + } + + if (razorPage == null) + { + throw new ArgumentNullException(nameof(razorPage)); + } + + if (htmlEncoder == null) + { + throw new ArgumentNullException(nameof(htmlEncoder)); + } + + if (diagnosticSource == null) + { + throw new ArgumentNullException(nameof(diagnosticSource)); + } + + _viewEngine = viewEngine; + _pageActivator = pageActivator; + ViewStartPages = viewStartPages; + RazorPage = razorPage; + _htmlEncoder = htmlEncoder; + _diagnosticSource = diagnosticSource; + } + + /// + public string Path => RazorPage.Path; + + /// + /// Gets instance that the views executes on. + /// + public IRazorPage RazorPage { get; } + + /// + /// Gets the sequence of _ViewStart instances that are executed by this view. + /// + public IReadOnlyList ViewStartPages { get; } + + /// + public virtual async Task RenderAsync(ViewContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + // This GetRequiredService call is by design. ViewBufferScope is a scoped service, RazorViewEngine + // is the component responsible for creating RazorViews and it is a Singleton service. It doesn't + // have access to the RequestServices so requiring the service when we render the page is the best + // we can do. + _bufferScope = context.HttpContext.RequestServices.GetRequiredService(); + var bodyWriter = await RenderPageAsync(RazorPage, context, invokeViewStarts: true); + await RenderLayoutAsync(context, bodyWriter); + } + + private async Task RenderPageAsync( + IRazorPage page, + ViewContext context, + bool invokeViewStarts) + { + var writer = context.Writer as ViewBufferTextWriter; + if (writer == null) + { + Debug.Assert(_bufferScope != null); + + // If we get here, this is likely the top-level page (not a partial) - this means + // that context.Writer is wrapping the output stream. We need to buffer, so create a buffered writer. + var buffer = new ViewBuffer(_bufferScope, page.Path, ViewBuffer.ViewPageSize); + writer = new ViewBufferTextWriter(buffer, context.Writer.Encoding, _htmlEncoder, context.Writer); + } + else + { + // This means we're writing something like a partial, where the output needs to be buffered. + // Create a new buffer, but without the ability to flush. + var buffer = new ViewBuffer(_bufferScope, page.Path, ViewBuffer.ViewPageSize); + writer = new ViewBufferTextWriter(buffer, context.Writer.Encoding); + } + + // The writer for the body is passed through the ViewContext, allowing things like HtmlHelpers + // and ViewComponents to reference it. + var oldWriter = context.Writer; + var oldFilePath = context.ExecutingFilePath; + + context.Writer = writer; + context.ExecutingFilePath = page.Path; + + try + { + if (invokeViewStarts) + { + // Execute view starts using the same context + writer as the page to render. + await RenderViewStartsAsync(context); + } + + await RenderPageCoreAsync(page, context); + return writer; + } + finally + { + context.Writer = oldWriter; + context.ExecutingFilePath = oldFilePath; + } + } + + private async Task RenderPageCoreAsync(IRazorPage page, ViewContext context) + { + page.ViewContext = context; + _pageActivator.Activate(page, context); + + _diagnosticSource.BeforeViewPage(page, context); + + try + { + await page.ExecuteAsync(); + } + finally + { + _diagnosticSource.AfterViewPage(page, context); + } + } + + private async Task RenderViewStartsAsync(ViewContext context) + { + string layout = null; + var oldFilePath = context.ExecutingFilePath; + try + { + for (var i = 0; i < ViewStartPages.Count; i++) + { + var viewStart = ViewStartPages[i]; + context.ExecutingFilePath = viewStart.Path; + + // If non-null, copy the layout value from the previous view start to the current. Otherwise leave + // Layout default alone. + if (layout != null) + { + viewStart.Layout = layout; + } + + await RenderPageCoreAsync(viewStart, context); + + // Pass correct absolute path to next layout or the entry page if this view start set Layout to a + // relative path. + layout = _viewEngine.GetAbsolutePath(viewStart.Path, viewStart.Layout); + } + } + finally + { + context.ExecutingFilePath = oldFilePath; + } + + // If non-null, copy the layout value from the view start page(s) to the entry page. + if (layout != null) + { + RazorPage.Layout = layout; + } + } + + private async Task RenderLayoutAsync( + ViewContext context, + ViewBufferTextWriter bodyWriter) + { + // A layout page can specify another layout page. We'll need to continue + // looking for layout pages until they're no longer specified. + var previousPage = RazorPage; + var renderedLayouts = new List(); + + // This loop will execute Layout pages from the inside to the outside. With each + // iteration, bodyWriter is replaced with the aggregate of all the "body" content + // (including the layout page we just rendered). + while (!string.IsNullOrEmpty(previousPage.Layout)) + { + if (!bodyWriter.IsBuffering) + { + // Once a call to RazorPage.FlushAsync is made, we can no longer render Layout pages - content has + // already been written to the client and the layout content would be appended rather than surround + // the body content. Throwing this exception wouldn't return a 500 (since content has already been + // written), but a diagnostic component should be able to capture it. + + var message = Resources.FormatLayoutCannotBeRendered(Path, nameof(Razor.RazorPage.FlushAsync)); + throw new InvalidOperationException(message); + } + + var layoutPage = GetLayoutPage(context, previousPage.Path, previousPage.Layout); + + if (renderedLayouts.Count > 0 && + renderedLayouts.Any(l => string.Equals(l.Path, layoutPage.Path, StringComparison.Ordinal))) + { + // If the layout has been previously rendered as part of this view, we're potentially in a layout + // rendering cycle. + throw new InvalidOperationException( + Resources.FormatLayoutHasCircularReference(previousPage.Path, layoutPage.Path)); + } + + // Notify the previous page that any writes that are performed on it are part of sections being written + // in the layout. + previousPage.IsLayoutBeingRendered = true; + layoutPage.PreviousSectionWriters = previousPage.SectionWriters; + layoutPage.BodyContent = bodyWriter.Buffer; + bodyWriter = await RenderPageAsync(layoutPage, context, invokeViewStarts: false); + + renderedLayouts.Add(layoutPage); + previousPage = layoutPage; + } + + // Now we've reached and rendered the outer-most layout page. Nothing left to execute. + + // Ensure all defined sections were rendered or RenderBody was invoked for page without defined sections. + foreach (var layoutPage in renderedLayouts) + { + layoutPage.EnsureRenderedBodyOrSections(); + } + + if (bodyWriter.IsBuffering) + { + // If IsBuffering - then we've got a bunch of content in the view buffer. How to best deal with it + // really depends on whether or not we're writing directly to the output or if we're writing to + // another buffer. + var viewBufferTextWriter = context.Writer as ViewBufferTextWriter; + if (viewBufferTextWriter == null || !viewBufferTextWriter.IsBuffering) + { + // This means we're writing to a 'real' writer, probably to the actual output stream. + // We're using PagedBufferedTextWriter here to 'smooth' synchronous writes of IHtmlContent values. + using (var writer = _bufferScope.CreateWriter(context.Writer)) + { + await bodyWriter.Buffer.WriteToAsync(writer, _htmlEncoder); + } + } + else + { + // This means we're writing to another buffer. Use MoveTo to combine them. + bodyWriter.Buffer.MoveTo(viewBufferTextWriter.Buffer); + } + } + } + + private IRazorPage GetLayoutPage(ViewContext context, string executingFilePath, string layoutPath) + { + var layoutPageResult = _viewEngine.GetPage(executingFilePath, layoutPath); + var originalLocations = layoutPageResult.SearchedLocations; + if (layoutPageResult.Page == null) + { + layoutPageResult = _viewEngine.FindPage(context, layoutPath); + } + + if (layoutPageResult.Page == null) + { + var locations = string.Empty; + if (originalLocations.Any()) + { + locations = Environment.NewLine + string.Join(Environment.NewLine, originalLocations); + } + + if (layoutPageResult.SearchedLocations.Any()) + { + locations += + Environment.NewLine + string.Join(Environment.NewLine, layoutPageResult.SearchedLocations); + } + + throw new InvalidOperationException(Resources.FormatLayoutCannotBeLocated(layoutPath, locations)); + } + + var layoutPage = layoutPageResult.Page; + return layoutPage; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs new file mode 100644 index 0000000000..8d929fb883 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs @@ -0,0 +1,520 @@ +// Copyright (c) .NET 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.Globalization; +using System.Linq; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Default implementation of . + /// + /// + /// For ViewResults returned from controllers, views should be located in + /// + /// by default. For the controllers in an area, views should exist in + /// . + /// + public class RazorViewEngine : IRazorViewEngine + { + public static readonly string ViewExtension = ".cshtml"; + private const string ViewStartFileName = "_ViewStart.cshtml"; + + private const string AreaKey = "area"; + private const string ControllerKey = "controller"; + private const string PageKey = "page"; + + private static readonly TimeSpan _cacheExpirationDuration = TimeSpan.FromMinutes(20); + + private readonly IRazorPageFactoryProvider _pageFactory; + private readonly IRazorPageActivator _pageActivator; + private readonly HtmlEncoder _htmlEncoder; + private readonly ILogger _logger; + private readonly RazorViewEngineOptions _options; + private readonly RazorProject _razorFileSystem; + private readonly DiagnosticSource _diagnosticSource; + + /// + /// Initializes a new instance of the . + /// + [Obsolete("This constructor is obsolete and will be removed in a future version.")] + public RazorViewEngine( + IRazorPageFactoryProvider pageFactory, + IRazorPageActivator pageActivator, + HtmlEncoder htmlEncoder, + IOptions optionsAccessor, + RazorProject razorProject, + ILoggerFactory loggerFactory, + DiagnosticSource diagnosticSource) + { + _options = optionsAccessor.Value; + + if (_options.ViewLocationFormats.Count == 0) + { + throw new ArgumentException( + Resources.FormatViewLocationFormatsIsRequired(nameof(RazorViewEngineOptions.ViewLocationFormats)), + nameof(optionsAccessor)); + } + + if (_options.AreaViewLocationFormats.Count == 0) + { + throw new ArgumentException( + Resources.FormatViewLocationFormatsIsRequired(nameof(RazorViewEngineOptions.AreaViewLocationFormats)), + nameof(optionsAccessor)); + } + + _pageFactory = pageFactory; + _pageActivator = pageActivator; + _htmlEncoder = htmlEncoder; + _logger = loggerFactory.CreateLogger(); + _razorFileSystem = razorProject; + _diagnosticSource = diagnosticSource; + ViewLookupCache = new MemoryCache(new MemoryCacheOptions()); + } + + /// + /// Initializes a new instance of the RazorViewEngine + /// + public RazorViewEngine( + IRazorPageFactoryProvider pageFactory, + IRazorPageActivator pageActivator, + HtmlEncoder htmlEncoder, + IOptions optionsAccessor, + RazorProjectFileSystem razorFileSystem, + ILoggerFactory loggerFactory, + DiagnosticSource diagnosticSource) +#pragma warning disable CS0618 // Type or member is obsolete + : this (pageFactory, pageActivator, htmlEncoder, optionsAccessor, (RazorProject)razorFileSystem, loggerFactory, diagnosticSource) +#pragma warning restore CS0618 // Type or member is obsolete + { + } + + /// + /// A cache for results of view lookups. + /// + protected IMemoryCache ViewLookupCache { get; } + + /// + /// Gets the case-normalized route value for the specified route . + /// + /// The . + /// The route key to lookup. + /// The value corresponding to the key. + /// + /// The casing of a route value in is determined by the client. + /// This making constructing paths for view locations in a case sensitive file system unreliable. Using the + /// to get route values + /// produces consistently cased results. + /// + public static string GetNormalizedRouteValue(ActionContext context, string key) + => NormalizedRouteValue.GetNormalizedRouteValue(context, key); + + /// + public RazorPageResult FindPage(ActionContext context, string pageName) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + if (IsApplicationRelativePath(pageName) || IsRelativePath(pageName)) + { + // A path; not a name this method can handle. + return new RazorPageResult(pageName, Enumerable.Empty()); + } + + var cacheResult = LocatePageFromViewLocations(context, pageName, isMainPage: false); + if (cacheResult.Success) + { + var razorPage = cacheResult.ViewEntry.PageFactory(); + return new RazorPageResult(pageName, razorPage); + } + else + { + return new RazorPageResult(pageName, cacheResult.SearchedLocations); + } + } + + /// + public RazorPageResult GetPage(string executingFilePath, string pagePath) + { + if (string.IsNullOrEmpty(pagePath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pagePath)); + } + + if (!(IsApplicationRelativePath(pagePath) || IsRelativePath(pagePath))) + { + // Not a path this method can handle. + return new RazorPageResult(pagePath, Enumerable.Empty()); + } + + var cacheResult = LocatePageFromPath(executingFilePath, pagePath, isMainPage: false); + if (cacheResult.Success) + { + var razorPage = cacheResult.ViewEntry.PageFactory(); + return new RazorPageResult(pagePath, razorPage); + } + else + { + return new RazorPageResult(pagePath, cacheResult.SearchedLocations); + } + } + + /// + public ViewEngineResult FindView(ActionContext context, string viewName, bool isMainPage) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (string.IsNullOrEmpty(viewName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewName)); + } + + if (IsApplicationRelativePath(viewName) || IsRelativePath(viewName)) + { + // A path; not a name this method can handle. + return ViewEngineResult.NotFound(viewName, Enumerable.Empty()); + } + + var cacheResult = LocatePageFromViewLocations(context, viewName, isMainPage); + return CreateViewEngineResult(cacheResult, viewName); + } + + /// + public ViewEngineResult GetView(string executingFilePath, string viewPath, bool isMainPage) + { + if (string.IsNullOrEmpty(viewPath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewPath)); + } + + if (!(IsApplicationRelativePath(viewPath) || IsRelativePath(viewPath))) + { + // Not a path this method can handle. + return ViewEngineResult.NotFound(viewPath, Enumerable.Empty()); + } + + var cacheResult = LocatePageFromPath(executingFilePath, viewPath, isMainPage); + return CreateViewEngineResult(cacheResult, viewPath); + } + + private ViewLocationCacheResult LocatePageFromPath(string executingFilePath, string pagePath, bool isMainPage) + { + var applicationRelativePath = GetAbsolutePath(executingFilePath, pagePath); + var cacheKey = new ViewLocationCacheKey(applicationRelativePath, isMainPage); + if (!ViewLookupCache.TryGetValue(cacheKey, out ViewLocationCacheResult cacheResult)) + { + var expirationTokens = new HashSet(); + cacheResult = CreateCacheResult(expirationTokens, applicationRelativePath, isMainPage); + + var cacheEntryOptions = new MemoryCacheEntryOptions(); + cacheEntryOptions.SetSlidingExpiration(_cacheExpirationDuration); + foreach (var expirationToken in expirationTokens) + { + cacheEntryOptions.AddExpirationToken(expirationToken); + } + + // No views were found at the specified location. Create a not found result. + if (cacheResult == null) + { + cacheResult = new ViewLocationCacheResult(new[] { applicationRelativePath }); + } + + cacheResult = ViewLookupCache.Set( + cacheKey, + cacheResult, + cacheEntryOptions); + } + + return cacheResult; + } + + private ViewLocationCacheResult LocatePageFromViewLocations( + ActionContext actionContext, + string pageName, + bool isMainPage) + { + var controllerName = GetNormalizedRouteValue(actionContext, ControllerKey); + var areaName = GetNormalizedRouteValue(actionContext, AreaKey); + string razorPageName = null; + if (actionContext.ActionDescriptor.RouteValues.ContainsKey(PageKey)) + { + // Only calculate the Razor Page name if "page" is registered in RouteValues. + razorPageName = GetNormalizedRouteValue(actionContext, PageKey); + } + + var expanderContext = new ViewLocationExpanderContext( + actionContext, + pageName, + controllerName, + areaName, + razorPageName, + isMainPage); + Dictionary expanderValues = null; + + if (_options.ViewLocationExpanders.Count > 0) + { + expanderValues = new Dictionary(StringComparer.Ordinal); + expanderContext.Values = expanderValues; + + // Perf: Avoid allocations + for (var i = 0; i < _options.ViewLocationExpanders.Count; i++) + { + _options.ViewLocationExpanders[i].PopulateValues(expanderContext); + } + } + + var cacheKey = new ViewLocationCacheKey( + expanderContext.ViewName, + expanderContext.ControllerName, + expanderContext.AreaName, + expanderContext.PageName, + expanderContext.IsMainPage, + expanderValues); + + if (!ViewLookupCache.TryGetValue(cacheKey, out ViewLocationCacheResult cacheResult)) + { + _logger.ViewLookupCacheMiss(cacheKey.ViewName, cacheKey.ControllerName); + cacheResult = OnCacheMiss(expanderContext, cacheKey); + } + else + { + _logger.ViewLookupCacheHit(cacheKey.ViewName, cacheKey.ControllerName); + } + + return cacheResult; + } + + /// + public string GetAbsolutePath(string executingFilePath, string pagePath) + { + if (string.IsNullOrEmpty(pagePath)) + { + // Path is not valid; no change required. + return pagePath; + } + + if (IsApplicationRelativePath(pagePath)) + { + // An absolute path already; no change required. + return pagePath; + } + + if (!IsRelativePath(pagePath)) + { + // A page name; no change required. + return pagePath; + } + + if (string.IsNullOrEmpty(executingFilePath)) + { + // Given a relative path i.e. not yet application-relative (starting with "~/" or "/"), interpret + // path relative to currently-executing view, if any. + // Not yet executing a view. Start in app root. + var absolutePath = "/" + pagePath; + return ViewEnginePath.ResolvePath(absolutePath); + } + + return ViewEnginePath.CombinePath(executingFilePath, pagePath); + } + + // internal for tests + internal IEnumerable GetViewLocationFormats(ViewLocationExpanderContext context) + { + if (!string.IsNullOrEmpty(context.AreaName) && + !string.IsNullOrEmpty(context.ControllerName)) + { + return _options.AreaViewLocationFormats; + } + else if (!string.IsNullOrEmpty(context.ControllerName)) + { + return _options.ViewLocationFormats; + } + else if (!string.IsNullOrEmpty(context.AreaName) && + !string.IsNullOrEmpty(context.PageName)) + { + return _options.AreaPageViewLocationFormats; + } + else if (!string.IsNullOrEmpty(context.PageName)) + { + return _options.PageViewLocationFormats; + } + else + { + // If we don't match one of these conditions, we'll just treat it like regular controller/action + // and use those search paths. This is what we did in 1.0.0 without giving much thought to it. + return _options.ViewLocationFormats; + } + } + + private ViewLocationCacheResult OnCacheMiss( + ViewLocationExpanderContext expanderContext, + ViewLocationCacheKey cacheKey) + { + var viewLocations = GetViewLocationFormats(expanderContext); + + for (var i = 0; i < _options.ViewLocationExpanders.Count; i++) + { + viewLocations = _options.ViewLocationExpanders[i].ExpandViewLocations(expanderContext, viewLocations); + } + + ViewLocationCacheResult cacheResult = null; + var searchedLocations = new List(); + var expirationTokens = new HashSet(); + foreach (var location in viewLocations) + { + var path = string.Format( + CultureInfo.InvariantCulture, + location, + expanderContext.ViewName, + expanderContext.ControllerName, + expanderContext.AreaName); + + path = ViewEnginePath.ResolvePath(path); + + cacheResult = CreateCacheResult(expirationTokens, path, expanderContext.IsMainPage); + if (cacheResult != null) + { + break; + } + + searchedLocations.Add(path); + } + + // No views were found at the specified location. Create a not found result. + if (cacheResult == null) + { + cacheResult = new ViewLocationCacheResult(searchedLocations); + } + + var cacheEntryOptions = new MemoryCacheEntryOptions(); + cacheEntryOptions.SetSlidingExpiration(_cacheExpirationDuration); + foreach (var expirationToken in expirationTokens) + { + cacheEntryOptions.AddExpirationToken(expirationToken); + } + + return ViewLookupCache.Set(cacheKey, cacheResult, cacheEntryOptions); + } + + // Internal for unit testing + internal ViewLocationCacheResult CreateCacheResult( + HashSet expirationTokens, + string relativePath, + bool isMainPage) + { + var factoryResult = _pageFactory.CreateFactory(relativePath); + var viewDescriptor = factoryResult.ViewDescriptor; + if (viewDescriptor?.ExpirationTokens != null) + { + for (var i = 0; i < viewDescriptor.ExpirationTokens.Count; i++) + { + expirationTokens.Add(viewDescriptor.ExpirationTokens[i]); + } + } + + if (factoryResult.Success) + { + // Only need to lookup _ViewStarts for the main page. + var viewStartPages = isMainPage ? + GetViewStartPages(viewDescriptor.RelativePath, expirationTokens) : + Array.Empty(); + if (viewDescriptor.IsPrecompiled) + { + _logger.PrecompiledViewFound(relativePath); + } + + return new ViewLocationCacheResult( + new ViewLocationCacheItem(factoryResult.RazorPageFactory, relativePath), + viewStartPages); + } + + return null; + } + + private IReadOnlyList GetViewStartPages( + string path, + HashSet expirationTokens) + { + var viewStartPages = new List(); + + foreach (var viewStartProjectItem in _razorFileSystem.FindHierarchicalItems(path, ViewStartFileName)) + { + var result = _pageFactory.CreateFactory(viewStartProjectItem.FilePath); + var viewDescriptor = result.ViewDescriptor; + if (viewDescriptor?.ExpirationTokens != null) + { + for (var i = 0; i < viewDescriptor.ExpirationTokens.Count; i++) + { + expirationTokens.Add(viewDescriptor.ExpirationTokens[i]); + } + } + + if (result.Success) + { + // Populate the viewStartPages list so that _ViewStarts appear in the order the need to be + // executed (closest last, furthest first). This is the reverse order in which + // ViewHierarchyUtility.GetViewStartLocations returns _ViewStarts. + viewStartPages.Insert(0, new ViewLocationCacheItem(result.RazorPageFactory, viewStartProjectItem.FilePath)); + } + } + + return viewStartPages; + } + + private ViewEngineResult CreateViewEngineResult(ViewLocationCacheResult result, string viewName) + { + if (!result.Success) + { + return ViewEngineResult.NotFound(viewName, result.SearchedLocations); + } + + var page = result.ViewEntry.PageFactory(); + + var viewStarts = new IRazorPage[result.ViewStartEntries.Count]; + for (var i = 0; i < viewStarts.Length; i++) + { + var viewStartItem = result.ViewStartEntries[i]; + viewStarts[i] = viewStartItem.PageFactory(); + } + + var view = new RazorView(this, _pageActivator, viewStarts, page, _htmlEncoder, _diagnosticSource); + return ViewEngineResult.Found(viewName, view); + } + + private static bool IsApplicationRelativePath(string name) + { + Debug.Assert(!string.IsNullOrEmpty(name)); + return name[0] == '~' || name[0] == '/'; + } + + private static bool IsRelativePath(string name) + { + Debug.Assert(!string.IsNullOrEmpty(name)); + + // Though ./ViewName looks like a relative path, framework searches for that view using view locations. + return name.EndsWith(ViewExtension, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs new file mode 100644 index 0000000000..532c924d32 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs @@ -0,0 +1,183 @@ +// Copyright (c) .NET 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.Hosting; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.CodeAnalysis; +using Microsoft.Extensions.FileProviders; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// Provides programmatic configuration for the . + /// + public class RazorViewEngineOptions + { + private Action _compilationCallback = c => { }; + + /// + /// Gets a used by the . + /// + public IList ViewLocationExpanders { get; } = new List(); + + /// + /// Gets the sequence of instances used by to + /// locate Razor files. + /// + /// + /// At startup, this is initialized to include an instance of + /// that is rooted at the application root. + /// + public IList FileProviders { get; } = new List(); + + /// + /// Gets the locations where will search for views. + /// + /// + /// + /// The locations of the views returned from controllers that do not belong to an area. + /// Locations are format strings (see https://msdn.microsoft.com/en-us/library/txafckwd.aspx) which may contain + /// the following format items: + /// + /// + /// + /// {0} - Action Name + /// + /// + /// {1} - Controller Name + /// + /// + /// + /// The values for these locations are case-sensitive on case-sensitive file systems. + /// For example, the view for the Test action of HomeController should be located at + /// /Views/Home/Test.cshtml. Locations such as /views/home/test.cshtml would not be discovered. + /// + /// + public IList ViewLocationFormats { get; } = new List(); + + /// + /// Gets the locations where will search for views within an + /// area. + /// + /// + /// + /// The locations of the views returned from controllers that belong to an area. + /// Locations are format strings (see https://msdn.microsoft.com/en-us/library/txafckwd.aspx) which may contain + /// the following format items: + /// + /// + /// + /// {0} - Action Name + /// + /// + /// {1} - Controller Name + /// + /// + /// {2} - Area Name + /// + /// + /// + /// The values for these locations are case-sensitive on case-sensitive file systems. + /// For example, the view for the Test action of HomeController under Admin area should + /// be located at /Areas/Admin/Views/Home/Test.cshtml. + /// Locations such as /areas/admin/views/home/test.cshtml would not be discovered. + /// + /// + public IList AreaViewLocationFormats { get; } = new List(); + + /// + /// Gets the locations where will search for views (such as layouts and partials) + /// when searched from the context of rendering a Razor Page. + /// + /// + /// + /// Locations are format strings (see https://msdn.microsoft.com/en-us/library/txafckwd.aspx) which may contain + /// the following format items: + /// + /// + /// + /// {0} - View Name + /// + /// + /// {1} - Page Name + /// + /// + /// + /// work in tandem with a view location expander to perform hierarchical + /// path lookups. For instance, given a Page like /Account/Manage/Index using /Pages as the root, the view engine + /// will search for views in the following locations: + /// + /// /Pages/Account/Manage/{0}.cshtml + /// /Pages/Account/{0}.cshtml + /// /Pages/{0}.cshtml + /// /Pages/Shared/{0}.cshtml + /// /Views/Shared/{0}.cshtml + /// + /// + public IList PageViewLocationFormats { get; } = new List(); + + /// + /// Gets the locations where will search for views (such as layouts and partials) + /// when searched from the context of rendering a Razor Page within an area. + /// + /// + /// + /// Locations are format strings (see https://msdn.microsoft.com/en-us/library/txafckwd.aspx) which may contain + /// the following format items: + /// + /// + /// + /// {0} - View Name + /// + /// + /// {1} - Page Name + /// + /// + /// {2} - Area Name + /// + /// + /// + /// work in tandem with a view location expander to perform hierarchical + /// path lookups. For instance, given a Page like /Areas/Account/Pages/Manage/User.cshtml using /Areas as the area pages root and + /// /Pages as the root, the view engine will search for views in the following locations: + /// + /// /Areas/Accounts/Pages/Manage/{0}.cshtml + /// /Areas/Accounts/Pages/{0}.cshtml + /// /Areas/Accounts/Pages/Shared/{0}.cshtml + /// /Areas/Accounts/Views/Shared/{0}.cshtml + /// /Pages/Shared/{0}.cshtml + /// /Views/Shared/{0}.cshtml + /// + /// + public IList AreaPageViewLocationFormats { get; } = new List(); + + /// + /// Gets the instances that should be included in Razor compilation, along with + /// those discovered by s. + /// + public IList AdditionalCompilationReferences { get; } = new List(); + + /// + /// Gets or sets the callback that is used to customize Razor compilation + /// to change compilation settings you can update property. + /// + /// + /// Customizations made here would not reflect in tooling (Intellisense). + /// + public Action CompilationCallback + { + get => _compilationCallback; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _compilationCallback = value; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RenderAsyncDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RenderAsyncDelegate.cs new file mode 100644 index 0000000000..ecdce26f06 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RenderAsyncDelegate.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. + +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + public delegate Task RenderAsyncDelegate(); +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx new file mode 100644 index 0000000000..19b0e8f053 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + Value cannot be null or empty. + + + One or more compilation failures occurred: + + + '{0}' cannot be invoked when a Layout page is set to be executed. + + + The layout view '{0}' could not be located. The following locations were searched:{1} + + + Layout page '{0}' cannot be rendered after '{1}' has been invoked. + + + There is no active writing scope to end. + + + The {0} operation cannot be performed while inside a writing scope in '{1}'. + + + {0} invocation in '{1}' is invalid. {0} can only be called from a layout page. + + + {0} has not been called for the page at '{1}'. To ignore call {2}(). + + + Section '{0}' is already defined. + + + {0} invocation in '{1}' is invalid. The section '{2}' has already been rendered. + + + The layout page '{0}' cannot find the section '{1}' in the content page '{2}'. + + + The following sections have been defined but have not been rendered by the page at '{0}': '{1}'. To ignore an unrendered section call {2}("sectionName"). + + + '{0} must be set to access '{1}'. + + + Generated Code + + + Unable to perform '{0}' assignment. Tag helper property '{1}.{2}' must not be null. + + + Unexpected return value from '{1}.{2}' for URL '{0}'. If the '{1}' service has been overridden, change '{2}' to replace only the '~/' prefix. Otherwise, add the following directive to the Razor page to disable URL resolution relative to the application's 'webroot' setting: + +@{3} "{4}, {5}" + + + 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. + + + '{0}' cannot be empty. These locations are required to locate a view for rendering. + + + Nesting of TagHelper attribute writing scopes is not supported. + + + '{0}.{1}' must not be empty. At least one '{2}' is required to locate a view for rendering. + + + Path must begin with a forward slash '/'. + + + The property '{0}' of '{1}' must not be null. + + + The following precompiled view paths differ only in case, which is not supported: + + + The debug type specified in the dependency context could be parsed. The debug type value '{0}' is not supported. + + + At least one of the '{0}' or '{1}' values must be non-null. + + \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelperInitializerOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelperInitializerOfT.cs new file mode 100644 index 0000000000..94d37f2dd8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelperInitializerOfT.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; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + public class TagHelperInitializer : ITagHelperInitializer + where TTagHelper : ITagHelper + { + private readonly Action _initializeDelegate; + + /// + /// Creates a . + /// + /// The initialization delegate. + public TagHelperInitializer(Action action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + _initializeDelegate = action; + } + + /// + public void Initialize(TTagHelper helper, ViewContext context) + { + if (helper == null) + { + throw new ArgumentNullException(nameof(helper)); + } + + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + _initializeDelegate(helper, context); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/BodyTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/BodyTagHelper.cs new file mode 100644 index 0000000000..111c67a0ab --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/BodyTagHelper.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 System.ComponentModel; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers +{ + /// + /// A targeting the <body> HTML element. + /// + [HtmlTargetElement("body")] + [EditorBrowsable(EditorBrowsableState.Never)] + public class BodyTagHelper : TagHelperComponentTagHelper + { + /// + /// Creates a new . + /// + /// The which contains the collection + /// of s. + /// The . + public BodyTagHelper(ITagHelperComponentManager manager, ILoggerFactory loggerFactory) + : base(manager, loggerFactory) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/HeadTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/HeadTagHelper.cs new file mode 100644 index 0000000000..131b89c6ba --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/HeadTagHelper.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 System.ComponentModel; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers +{ + /// + /// A targeting the <head> HTML element. + /// + [HtmlTargetElement("head")] + [EditorBrowsable(EditorBrowsableState.Never)] + public class HeadTagHelper : TagHelperComponentTagHelper + { + /// + /// Creates a new . + /// + /// The which contains the collection + /// of s. + /// The . + public HeadTagHelper(ITagHelperComponentManager manager, ILoggerFactory loggerFactory) + : base(manager, loggerFactory) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentManager.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentManager.cs new file mode 100644 index 0000000000..813372d558 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentManager.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 System.Collections.Generic; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers +{ + /// + /// An implementation of this interface provides the collection of s + /// that will be used by s. + /// + public interface ITagHelperComponentManager + { + /// + /// Gets the collection of s that will be used by + /// s. + /// + ICollection Components { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentPropertyActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentPropertyActivator.cs new file mode 100644 index 0000000000..f502fd90c3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentPropertyActivator.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.Mvc.Rendering; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers +{ + /// + /// Provides methods to activate properties of s. + /// + public interface ITagHelperComponentPropertyActivator + { + /// + /// Activates properties of the . + /// + /// The for the executing view. + /// The to activate properties of. + void Activate(ViewContext context, ITagHelperComponent tagHelperComponent); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentPropertyActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentPropertyActivator.cs new file mode 100644 index 0000000000..7da3e3ee4d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentPropertyActivator.cs @@ -0,0 +1,60 @@ +// Copyright (c) .NET 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.Reflection; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers +{ + /// + /// Default implementation of . + /// + internal class TagHelperComponentPropertyActivator : ITagHelperComponentPropertyActivator + { + private readonly ConcurrentDictionary[]> _propertiesToActivate; + private readonly Func[]> _getPropertiesToActivate = GetPropertiesToActivate; + private static readonly Func> _createActivateInfo = CreateActivateInfo; + + public TagHelperComponentPropertyActivator() + { + _propertiesToActivate = new ConcurrentDictionary[]>(); + } + + /// + public void Activate(ViewContext context, ITagHelperComponent tagHelperComponent) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var propertiesToActivate = _propertiesToActivate.GetOrAdd( + tagHelperComponent.GetType(), + _getPropertiesToActivate); + + for (var i = 0; i < propertiesToActivate.Length; i++) + { + var activateInfo = propertiesToActivate[i]; + activateInfo.Activate(tagHelperComponent, context); + } + } + + private static PropertyActivator CreateActivateInfo(PropertyInfo property) + { + return new PropertyActivator(property, viewContext => viewContext); + } + + private static PropertyActivator[] GetPropertiesToActivate(Type type) + { + return PropertyActivator.GetPropertiesToActivate( + type, + typeof(ViewContextAttribute), + _createActivateInfo); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentTagHelper.cs new file mode 100644 index 0000000000..ddf68268bd --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentTagHelper.cs @@ -0,0 +1,97 @@ +// Copyright (c) .NET 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.Razor.Internal; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers +{ + /// + /// Initializes and processes the s added to the + /// in the specified order. + /// + public abstract class TagHelperComponentTagHelper : TagHelper + { + private readonly ILogger _logger; + private readonly IEnumerable _components; + + /// + /// Creates a new and orders the + /// the collection of s in . + /// + /// The which contains the collection + /// of s. + /// The . + /// The are ordered after the + /// creation of the to position the s + /// added from controllers and views correctly. + public TagHelperComponentTagHelper( + ITagHelperComponentManager manager, + ILoggerFactory loggerFactory) + { + if (manager == null) + { + throw new ArgumentNullException(nameof(manager)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _components = manager.Components.OrderBy(p => p.Order).ToArray(); + _logger = loggerFactory.CreateLogger(GetType()); + } + + /// + /// Activates the property of all the . + /// + [HtmlAttributeNotBound] + public ITagHelperComponentPropertyActivator PropertyActivator { get; set; } + + [ViewContext] + [HtmlAttributeNotBound] + public ViewContext ViewContext { get; set; } + + /// + public override void Init(TagHelperContext context) + { + if (PropertyActivator == null) + { + var serviceProvider = ViewContext.HttpContext.RequestServices; + PropertyActivator = serviceProvider.GetRequiredService(); + } + + foreach (var component in _components) + { + PropertyActivator.Activate(ViewContext, component); + component.Init(context); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.TagHelperComponentInitialized(component.GetType().FullName); + } + } + } + + /// + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + foreach (var component in _components) + { + await component.ProcessAsync(context, output); + if (_logger.IsEnabled(LogLevel.Debug)) + { + _logger.TagHelperComponentProcessed(component.GetType().FullName); + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeature.cs new file mode 100644 index 0000000000..1f6561bd4d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeature.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.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers +{ + /// + /// The list of tag helper types in an MVC application. The can be populated + /// using the that is available during startup at + /// and or at a later stage by requiring the + /// as a dependency in a component. + /// + public class TagHelperFeature + { + /// + /// Gets the list of tag helper types in an MVC application. + /// + public IList TagHelpers { get; } = new List(); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeatureProvider.cs new file mode 100644 index 0000000000..1b24fb7480 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeatureProvider.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.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers +{ + public class TagHelperFeatureProvider : IApplicationFeatureProvider + { + public void PopulateFeature(IEnumerable parts, TagHelperFeature feature) + { + foreach (var part in parts) + { + if (IncludePart(part) && part is IApplicationPartTypeProvider typeProvider) + { + foreach (var type in typeProvider.Types) + { + var typeInfo = type.GetTypeInfo(); + if (IncludeType(typeInfo) && !feature.TagHelpers.Contains(typeInfo)) + { + feature.TagHelpers.Add(typeInfo); + } + } + } + } + } + + protected virtual bool IncludePart(ApplicationPart part) => true; + + protected virtual bool IncludeType(TypeInfo type) + { + // We don't need to check visibility here, that's handled by the type provider. + return + typeof(ITagHelper).GetTypeInfo().IsAssignableFrom(type) && + !type.IsAbstract && + !type.IsGenericType; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/UrlResolutionTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/UrlResolutionTagHelper.cs new file mode 100644 index 0000000000..21ae7d4ce3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/UrlResolutionTagHelper.cs @@ -0,0 +1,370 @@ +// Copyright (c) .NET 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.IO; +using System.Reflection; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.Razor.TagHelpers +{ + /// + /// implementation targeting elements containing attributes with URL expected values. + /// + /// Resolves URLs starting with '~/' (relative to the application's 'webroot' setting) that are not + /// targeted by other s. Runs prior to other s to ensure + /// application-relative URLs are resolved. + [HtmlTargetElement("*", Attributes = "[itemid^='~/']")] + [HtmlTargetElement("a", Attributes = "[href^='~/']")] + [HtmlTargetElement("applet", Attributes = "[archive^='~/']")] + [HtmlTargetElement("area", Attributes = "[href^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("audio", Attributes = "[src^='~/']")] + [HtmlTargetElement("base", Attributes = "[href^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("blockquote", Attributes = "[cite^='~/']")] + [HtmlTargetElement("button", Attributes = "[formaction^='~/']")] + [HtmlTargetElement("del", Attributes = "[cite^='~/']")] + [HtmlTargetElement("embed", Attributes = "[src^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("form", Attributes = "[action^='~/']")] + [HtmlTargetElement("html", Attributes = "[manifest^='~/']")] + [HtmlTargetElement("iframe", Attributes = "[src^='~/']")] + [HtmlTargetElement("img", Attributes = "[src^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("img", Attributes = "[srcset^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = "[src^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = "[formaction^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("ins", Attributes = "[cite^='~/']")] + [HtmlTargetElement("link", Attributes = "[href^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("menuitem", Attributes = "[icon^='~/']")] + [HtmlTargetElement("object", Attributes = "[archive^='~/']")] + [HtmlTargetElement("object", Attributes = "[data^='~/']")] + [HtmlTargetElement("q", Attributes = "[cite^='~/']")] + [HtmlTargetElement("script", Attributes = "[src^='~/']")] + [HtmlTargetElement("source", Attributes = "[src^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("source", Attributes = "[srcset^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("track", Attributes = "[src^='~/']", TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("video", Attributes = "[src^='~/']")] + [HtmlTargetElement("video", Attributes = "[poster^='~/']")] + [EditorBrowsable(EditorBrowsableState.Never)] + public class UrlResolutionTagHelper : TagHelper + { + // Valid whitespace characters defined by the HTML5 spec. + private static readonly char[] ValidAttributeWhitespaceChars = + new[] { '\t', '\n', '\u000C', '\r', ' ' }; + private static readonly Dictionary ElementAttributeLookups = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "a", new[] { "href" } }, + { "applet", new[] { "archive" } }, + { "area", new[] { "href" } }, + { "audio", new[] { "src" } }, + { "base", new[] { "href" } }, + { "blockquote", new[] { "cite" } }, + { "button", new[] { "formaction" } }, + { "del", new[] { "cite" } }, + { "embed", new[] { "src" } }, + { "form", new[] { "action" } }, + { "html", new[] { "manifest" } }, + { "iframe", new[] { "src" } }, + { "img", new[] { "src", "srcset" } }, + { "input", new[] { "src", "formaction" } }, + { "ins", new[] { "cite" } }, + { "link", new[] { "href" } }, + { "menuitem", new[] { "icon" } }, + { "object", new[] { "archive", "data" } }, + { "q", new[] { "cite" } }, + { "script", new[] { "src" } }, + { "source", new[] { "src", "srcset" } }, + { "track", new[] { "src" } }, + { "video", new[] { "poster", "src" } }, + }; + + /// + /// Creates a new . + /// + /// The . + /// The . + public UrlResolutionTagHelper(IUrlHelperFactory urlHelperFactory, HtmlEncoder htmlEncoder) + { + UrlHelperFactory = urlHelperFactory; + HtmlEncoder = htmlEncoder; + } + + /// + public override int Order => -1000 - 999; + + protected IUrlHelperFactory UrlHelperFactory { get; } + + protected HtmlEncoder HtmlEncoder { get; } + + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + + /// + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (output.TagName == null) + { + return; + } + + string[] attributeNames; + if (ElementAttributeLookups.TryGetValue(output.TagName, out attributeNames)) + { + for (var i = 0; i < attributeNames.Length; i++) + { + ProcessUrlAttribute(attributeNames[i], output); + } + } + + // itemid can be present on any HTML element. + ProcessUrlAttribute("itemid", output); + } + + /// + /// Resolves and updates URL values starting with '~/' (relative to the application's 'webroot' setting) for + /// 's whose + /// is . + /// + /// The attribute name used to lookup values to resolve. + /// The . + protected void ProcessUrlAttribute(string attributeName, TagHelperOutput output) + { + if (attributeName == null) + { + throw new ArgumentNullException(nameof(attributeName)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + for (var i = 0; i < output.Attributes.Count; i++) + { + var attribute = output.Attributes[i]; + if (!string.Equals(attribute.Name, attributeName, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + var stringValue = attribute.Value as string; + if (stringValue != null) + { + string resolvedUrl; + if (TryResolveUrl(stringValue, resolvedUrl: out resolvedUrl)) + { + output.Attributes[i] = new TagHelperAttribute( + attribute.Name, + resolvedUrl, + attribute.ValueStyle); + } + } + else + { + var htmlContent = attribute.Value as IHtmlContent; + if (htmlContent != null) + { + var htmlString = htmlContent as HtmlString; + if (htmlString != null) + { + // No need for a StringWriter in this case. + stringValue = htmlString.ToString(); + } + else + { + using (var writer = new StringWriter()) + { + htmlContent.WriteTo(writer, HtmlEncoder); + stringValue = writer.ToString(); + } + } + + IHtmlContent resolvedUrl; + if (TryResolveUrl(stringValue, resolvedUrl: out resolvedUrl)) + { + output.Attributes[i] = new TagHelperAttribute( + attribute.Name, + resolvedUrl, + attribute.ValueStyle); + } + else if (htmlString == null) + { + // Not a ~/ URL. Just avoid re-encoding the attribute value later. + output.Attributes[i] = new TagHelperAttribute( + attribute.Name, + new HtmlString(stringValue), + attribute.ValueStyle); + } + } + } + } + } + + /// + /// Tries to resolve the given value relative to the application's 'webroot' setting. + /// + /// The URL to resolve. + /// Absolute URL beginning with the application's virtual root. null if + /// could not be resolved. + /// true if the could be resolved; false otherwise. + protected bool TryResolveUrl(string url, out string resolvedUrl) + { + resolvedUrl = null; + var start = FindRelativeStart(url); + if (start == -1) + { + return false; + } + + var trimmedUrl = CreateTrimmedString(url, start); + + var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext); + resolvedUrl = urlHelper.Content(trimmedUrl); + + return true; + } + + /// + /// Tries to resolve the given value relative to the application's 'webroot' setting. + /// + /// The URL to resolve. + /// + /// Absolute URL beginning with the application's virtual root. null if could + /// not be resolved. + /// + /// true if the could be resolved; false otherwise. + protected bool TryResolveUrl(string url, out IHtmlContent resolvedUrl) + { + resolvedUrl = null; + var start = FindRelativeStart(url); + if (start == -1) + { + return false; + } + + var trimmedUrl = CreateTrimmedString(url, start); + + var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext); + var appRelativeUrl = urlHelper.Content(trimmedUrl); + var postTildeSlashUrlValue = trimmedUrl.Substring(2); + + if (!appRelativeUrl.EndsWith(postTildeSlashUrlValue, StringComparison.Ordinal)) + { + throw new InvalidOperationException( + Resources.FormatCouldNotResolveApplicationRelativeUrl_TagHelper( + url, + nameof(IUrlHelper), + nameof(IUrlHelper.Content), + "removeTagHelper", + typeof(UrlResolutionTagHelper).FullName, + typeof(UrlResolutionTagHelper).GetTypeInfo().Assembly.GetName().Name)); + } + + resolvedUrl = new EncodeFirstSegmentContent( + appRelativeUrl, + appRelativeUrl.Length - postTildeSlashUrlValue.Length, + postTildeSlashUrlValue); + + return true; + } + + private static int FindRelativeStart(string url) + { + if (url == null || url.Length < 2) + { + return -1; + } + + var maxTestLength = url.Length - 2; + + var start = 0; + for (; start < url.Length; start++) + { + if (start > maxTestLength) + { + return -1; + } + + if (!IsCharWhitespace(url[start])) + { + break; + } + } + + // Before doing more work, ensure that the URL we're looking at is app-relative. + if (url[start] != '~' || url[start + 1] != '/') + { + return -1; + } + + return start; + } + + private static string CreateTrimmedString(string input, int start) + { + var end = input.Length - 1; + for (; end >= start; end--) + { + if (!IsCharWhitespace(input[end])) + { + break; + } + } + + var len = end - start + 1; + + // Substring returns same string if start == 0 && len == Length + return input.Substring(start, len); + } + + private static bool IsCharWhitespace(char ch) + { + for (var i = 0; i < ValidAttributeWhitespaceChars.Length; i++) + { + if (ValidAttributeWhitespaceChars[i] == ch) + { + return true; + } + } + // the character is not white space + return false; + } + + private class EncodeFirstSegmentContent : IHtmlContent + { + private readonly string _firstSegment; + private readonly int _firstSegmentLength; + private readonly string _secondSegment; + + public EncodeFirstSegmentContent(string firstSegment, int firstSegmentLength, string secondSegment) + { + _firstSegment = firstSegment; + _firstSegmentLength = firstSegmentLength; + _secondSegment = secondSegment; + } + + public void WriteTo(TextWriter writer, HtmlEncoder encoder) + { + encoder.Encode(writer, _firstSegment, 0, _firstSegmentLength); + writer.Write(_secondSegment); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ViewLocationExpanderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ViewLocationExpanderContext.cs new file mode 100644 index 0000000000..0ecc042ed3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ViewLocationExpanderContext.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 System.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Razor +{ + /// + /// A context for containing information for . + /// + public class ViewLocationExpanderContext + { + /// + /// Initializes a new instance of . + /// + /// The for the current executing action. + /// The view name. + /// The controller name. + /// The area name. + /// The page name. + /// Determines if the page being found is the main page for an action. + public ViewLocationExpanderContext( + ActionContext actionContext, + string viewName, + string controllerName, + string areaName, + string pageName, + bool isMainPage) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + if (viewName == null) + { + throw new ArgumentNullException(nameof(viewName)); + } + + ActionContext = actionContext; + ViewName = viewName; + ControllerName = controllerName; + AreaName = areaName; + PageName = pageName; + IsMainPage = isMainPage; + } + + /// + /// Gets the for the current executing action. + /// + public ActionContext ActionContext { get; } + + /// + /// Gets the view name. + /// + public string ViewName { get; } + + /// + /// Gets the controller name. + /// + public string ControllerName { get; } + + /// + /// Gets the page name. This will be the value of the page route value when rendering a Page from the + /// Razor Pages framework. This value will be null if rendering a view as the result of a controller. + /// + public string PageName { get; } + + /// + /// Gets the area name. + /// + public string AreaName { get; } + + /// + /// Determines if the page being found is the main page for an action. + /// + public bool IsMainPage { get; } + + /// + /// Gets or sets the that is populated with values as part of + /// . + /// + public IDictionary Values { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/baseline.netcore.json new file mode 100644 index 0000000000..daae245f2e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/baseline.netcore.json @@ -0,0 +1,3455 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Razor, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.HelperResult", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Html.IHtmlContent" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_WriteAction", + "Parameters": [], + "ReturnType": "System.Func", + "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", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContent", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "asyncAction", + "Type": "System.Func" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ViewContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BodyContent", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BodyContent", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Html.IHtmlContent" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsLayoutBeingRendered", + "Parameters": [], + "ReturnType": "System.Boolean", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsLayoutBeingRendered", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Path", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Path", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Layout", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Layout", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PreviousSectionWriters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PreviousSectionWriters", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IDictionary" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SectionWriters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EnsureRenderedBodyOrSections", + "Parameters": [], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.IRazorPageActivator", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Activate", + "Parameters": [ + { + "Name": "page", + "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage" + }, + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.IRazorPageFactoryProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateFactory", + "Parameters": [ + { + "Name": "relativePath", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.RazorPageFactoryResult", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ViewEngines.IViewEngine" + ], + "Members": [ + { + "Kind": "Method", + "Name": "FindPage", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.RazorPageResult", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetPage", + "Parameters": [ + { + "Name": "executingFilePath", + "Type": "System.String" + }, + { + "Name": "pagePath", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.RazorPageResult", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAbsolutePath", + "Parameters": [ + { + "Name": "executingFilePath", + "Type": "System.String" + }, + { + "Name": "pagePath", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.ITagHelperActivator", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Create", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "T0", + "GenericParameter": [ + { + "ParameterName": "TTagHelper", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper" + ] + } + ] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.ITagHelperFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateTagHelper", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "T0", + "GenericParameter": [ + { + "ParameterName": "TTagHelper", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper" + ] + } + ] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.ITagHelperInitializer", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Initialize", + "Parameters": [ + { + "Name": "helper", + "Type": "T0" + }, + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TTagHelper", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper" + ] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.IViewLocationExpander", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "PopulateValues", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Razor.ViewLocationExpanderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExpandViewLocations", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Razor.ViewLocationExpanderContext" + }, + { + "Name": "viewLocations", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpander", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Razor.IViewLocationExpander" + ], + "Members": [ + { + "Kind": "Method", + "Name": "PopulateValues", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Razor.ViewLocationExpanderContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IViewLocationExpander", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExpandViewLocations", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Razor.ViewLocationExpanderContext" + }, + { + "Name": "viewLocations", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IViewLocationExpander", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "format", + "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat", + "Visibility": "Public", + "Kind": "Enumeration", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Field", + "Name": "SubFolder", + "Parameters": [], + "GenericParameter": [], + "Literal": "0" + }, + { + "Kind": "Field", + "Name": "Suffix", + "Parameters": [], + "GenericParameter": [], + "Literal": "1" + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.RazorPage", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RenderBody", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IgnoreBody", + "Parameters": [], + "ReturnType": "System.Void", + "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, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IsSectionDefined", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RenderSection", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Html.HtmlString", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RenderSection", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "required", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Html.HtmlString", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RenderSectionAsync", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RenderSectionAsync", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "required", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IgnoreSection", + "Parameters": [ + { + "Name": "sectionName", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "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, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EndContext", + "Parameters": [], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.RazorPageActivator", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Razor.IRazorPageActivator" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Activate", + "Parameters": [ + { + "Name": "page", + "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage" + }, + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPageActivator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "metadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "urlHelperFactory", + "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" + }, + { + "Name": "jsonHelper", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper" + }, + { + "Name": "diagnosticSource", + "Type": "System.Diagnostics.DiagnosticSource" + }, + { + "Name": "htmlEncoder", + "Type": "System.Text.Encodings.Web.HtmlEncoder" + }, + { + "Name": "modelExpressionProvider", + "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.RazorPageBase", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Layout", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Layout", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Output", + "Parameters": [], + "ReturnType": "System.IO.TextWriter", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Path", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Path", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SectionWriters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewBag", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsLayoutBeingRendered", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsLayoutBeingRendered", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BodyContent", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BodyContent", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Html.IHtmlContent" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PreviousSectionWriters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PreviousSectionWriters", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IDictionary" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DiagnosticSource", + "Parameters": [], + "ReturnType": "System.Diagnostics.DiagnosticSource", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DiagnosticSource", + "Parameters": [ + { + "Name": "value", + "Type": "System.Diagnostics.DiagnosticSource" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HtmlEncoder", + "Parameters": [], + "ReturnType": "System.Text.Encodings.Web.HtmlEncoder", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HtmlEncoder", + "Parameters": [ + { + "Name": "value", + "Type": "System.Text.Encodings.Web.HtmlEncoder" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_User", + "Parameters": [], + "ReturnType": "System.Security.Claims.ClaimsPrincipal", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_TempData", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Abstract": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "InvalidTagHelperIndexerAssignment", + "Parameters": [ + { + "Name": "attributeName", + "Type": "System.String" + }, + { + "Name": "tagHelperTypeName", + "Type": "System.String" + }, + { + "Name": "propertyName", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateTagHelper", + "Parameters": [], + "ReturnType": "T0", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TTagHelper", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "StartTagHelperWritingScope", + "Parameters": [ + { + "Name": "encoder", + "Type": "System.Text.Encodings.Web.HtmlEncoder" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EndTagHelperWritingScope", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContent", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BeginWriteTagHelperAttribute", + "Parameters": [], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EndWriteTagHelperAttribute", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "PushWriter", + "Parameters": [ + { + "Name": "writer", + "Type": "System.IO.TextWriter" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "PopWriter", + "Parameters": [], + "ReturnType": "System.IO.TextWriter", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Href", + "Parameters": [ + { + "Name": "contentPath", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "DefineSection", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "section", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Write", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Write", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteLiteral", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteLiteral", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BeginWriteAttribute", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "prefix", + "Type": "System.String" + }, + { + "Name": "prefixOffset", + "Type": "System.Int32" + }, + { + "Name": "suffix", + "Type": "System.String" + }, + { + "Name": "suffixOffset", + "Type": "System.Int32" + }, + { + "Name": "attributeValuesCount", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteAttributeValue", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + }, + { + "Name": "prefixOffset", + "Type": "System.Int32" + }, + { + "Name": "value", + "Type": "System.Object" + }, + { + "Name": "valueOffset", + "Type": "System.Int32" + }, + { + "Name": "valueLength", + "Type": "System.Int32" + }, + { + "Name": "isLiteral", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EndWriteAttribute", + "Parameters": [], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BeginAddHtmlAttributeValues", + "Parameters": [ + { + "Name": "executionContext", + "Type": "Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext" + }, + { + "Name": "attributeName", + "Type": "System.String" + }, + { + "Name": "attributeValuesCount", + "Type": "System.Int32" + }, + { + "Name": "attributeValueStyle", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.HtmlAttributeValueStyle" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddHtmlAttributeValue", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + }, + { + "Name": "prefixOffset", + "Type": "System.Int32" + }, + { + "Name": "value", + "Type": "System.Object" + }, + { + "Name": "valueOffset", + "Type": "System.Int32" + }, + { + "Name": "valueLength", + "Type": "System.Int32" + }, + { + "Name": "isLiteral", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EndAddHtmlAttributeValues", + "Parameters": [ + { + "Name": "executionContext", + "Type": "Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "FlushAsync", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetAntiforgeryCookieAndHeader", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Html.HtmlString", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.RazorPageFactoryResult", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_RazorPageFactory", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewDescriptor", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompiledViewDescriptor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Success", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "viewDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompiledViewDescriptor" + }, + { + "Name": "razorPageFactory", + "Type": "System.Func" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.RazorPage", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "Microsoft.AspNetCore.Mvc.Razor.RazorPage", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Model", + "Parameters": [], + "ReturnType": "T0", + "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": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.RazorPageResult", + "Visibility": "Public", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Page", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SearchedLocations", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "page", + "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "searchedLocations", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.RazorView", + "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": "get_RazorPage", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewStartPages", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RenderAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewEngines.IView", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "viewEngine", + "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine" + }, + { + "Name": "pageActivator", + "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorPageActivator" + }, + { + "Name": "viewStartPages", + "Type": "System.Collections.Generic.IReadOnlyList" + }, + { + "Name": "razorPage", + "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage" + }, + { + "Name": "htmlEncoder", + "Type": "System.Text.Encodings.Web.HtmlEncoder" + }, + { + "Name": "diagnosticSource", + "Type": "System.Diagnostics.DiagnosticSource" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "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", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Caching.Memory.IMemoryCache", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "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": [] + }, + { + "Kind": "Method", + "Name": "FindPage", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.RazorPageResult", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetPage", + "Parameters": [ + { + "Name": "executingFilePath", + "Type": "System.String" + }, + { + "Name": "pagePath", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.RazorPageResult", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetAbsolutePath", + "Parameters": [ + { + "Name": "executingFilePath", + "Type": "System.String" + }, + { + "Name": "pagePath", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine", + "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": "razorProject", + "Type": "Microsoft.AspNetCore.Razor.Language.RazorProject" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "diagnosticSource", + "Type": "System.Diagnostics.DiagnosticSource" + } + ], + "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", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ViewLocationExpanders", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_FileProviders", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewLocationFormats", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AreaViewLocationFormats", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PageViewLocationFormats", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AreaPageViewLocationFormats", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AdditionalCompilationReferences", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_CompilationCallback", + "Parameters": [], + "ReturnType": "System.Action", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_CompilationCallback", + "Parameters": [ + { + "Name": "value", + "Type": "System.Action" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.RenderAsyncDelegate", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.MulticastDelegate", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Invoke", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BeginInvoke", + "Parameters": [ + { + "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.Razor.TagHelperInitializer", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Razor.ITagHelperInitializer" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Initialize", + "Parameters": [ + { + "Name": "helper", + "Type": "T0" + }, + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.ITagHelperInitializer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "action", + "Type": "System.Action" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TTagHelper", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper" + ] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.ViewLocationExpanderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "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_PageName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AreaName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsMainPage", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Values", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Values", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "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" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.BodyTagHelper", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.TagHelperComponentTagHelper", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "manager", + "Type": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentManager" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.HeadTagHelper", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.TagHelperComponentTagHelper", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "manager", + "Type": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentManager" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentManager", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Components", + "Parameters": [], + "ReturnType": "System.Collections.Generic.ICollection", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "Abstract": true, + "BaseType": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelper", + "ImplementedInterfaces": [], + "Members": [ + { + "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": "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_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", + "Parameters": [ + { + "Name": "manager", + "Type": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentManager" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.TagHelperFeature", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_TagHelpers", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.TagHelperFeatureProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "PopulateFeature", + "Parameters": [ + { + "Name": "parts", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "feature", + "Type": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.TagHelperFeature" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IncludePart", + "Parameters": [ + { + "Name": "part", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPart" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "IncludeType", + "Parameters": [ + { + "Name": "type", + "Type": "System.Reflection.TypeInfo" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.UrlResolutionTagHelper", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelper", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Int32", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_UrlHelperFactory", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HtmlEncoder", + "Parameters": [], + "ReturnType": "System.Text.Encodings.Web.HtmlEncoder", + "Visibility": "Protected", + "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": "Process", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" + }, + { + "Name": "output", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ProcessUrlAttribute", + "Parameters": [ + { + "Name": "attributeName", + "Type": "System.String" + }, + { + "Name": "output", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" + } + ], + "ReturnType": "System.Void", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryResolveUrl", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + }, + { + "Name": "resolvedUrl", + "Type": "System.String", + "Direction": "Out" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryResolveUrl", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + }, + { + "Name": "resolvedUrl", + "Type": "Microsoft.AspNetCore.Html.IHtmlContent", + "Direction": "Out" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "urlHelperFactory", + "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" + }, + { + "Name": "htmlEncoder", + "Type": "System.Text.Encodings.Web.HtmlEncoder" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationFailedException", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Exception", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Diagnostics.ICompilationException" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_CompilationFailures", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Diagnostics.ICompilationException", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "compilationFailures", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompiledViewDescriptor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "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_ViewAttribute", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewAttribute", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExpirationTokens", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ExpirationTokens", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsPrecompiled", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsPrecompiled", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "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": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.IViewCompiler", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CompileAsync", + "Parameters": [ + { + "Name": "relativePath", + "Type": "System.String" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.IViewCompilerProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetCompiler", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.Compilation.IViewCompiler", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.MetadataReferenceFeature", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_MetadataReferences", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.MetadataReferenceFeatureProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "PopulateFeature", + "Parameters": [ + { + "Name": "parts", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "feature", + "Type": "Microsoft.AspNetCore.Mvc.Razor.Compilation.MetadataReferenceFeature" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorReferenceManager", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_CompilationReferences", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Path", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "path", + "Type": "System.String" + }, + { + "Name": "viewType", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.RoslynCompilationContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Compilation", + "Parameters": [], + "ReturnType": "Microsoft.CodeAnalysis.CSharp.CSharpCompilation", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Compilation", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.CodeAnalysis.CSharp.CSharpCompilation" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "compilation", + "Type": "Microsoft.CodeAnalysis.CSharp.CSharpCompilation" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.ViewsFeature", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ViewDescriptors", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.ViewsFeatureProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "PopulateFeature", + "Parameters": [ + { + "Name": "parts", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "feature", + "Type": "Microsoft.AspNetCore.Mvc.Razor.Compilation.ViewsFeature" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetViewAttributes", + "Parameters": [ + { + "Name": "assemblyPart", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationParts.AssemblyPart" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "PrecompiledViewsAssemblySuffix", + "Parameters": [], + "ReturnType": "System.String", + "Static": true, + "ReadOnly": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "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", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddRazorOptions", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddTagHelpersAsServices", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "InitializeTagHelper", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "initialize", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TTagHelper", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper" + ] + } + ] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcRazorMvcCoreBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddRazorViewEngine", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddRazorViewEngine", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddTagHelpersAsServices", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "InitializeTagHelper", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "initialize", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TTagHelper", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper" + ] + } + ] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs new file mode 100644 index 0000000000..e1b6d778d5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.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.ApplicationModels +{ + /// + /// Allows customization of the . + /// + public interface IPageApplicationModelConvention : IPageConvention + { + /// + /// Called to apply the convention to the . + /// + /// The . + void Apply(PageApplicationModel model); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelProvider.cs new file mode 100644 index 0000000000..9296955e2e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// Builds or modifies an for Razor Page discovery. + /// + public interface IPageApplicationModelProvider + { + /// + /// Gets the order value for determining the order of execution of providers. Providers execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Providers are executed in an ordering determined by an ascending sort of the property. + /// A provider with a lower numeric value of will have its + /// called before that of a provider with a higher numeric value of + /// . The method is called in the reverse ordering after + /// all calls to . A provider with a lower numeric value of + /// will have its method called after that of a provider + /// with a higher numeric value of . + /// + /// + /// If two providers have the same numeric value of , then their relative execution order + /// is undefined. + /// + /// + int Order { get; } + + /// + /// Executed for the first pass of building instances. See . + /// + /// The . + void OnProvidersExecuting(PageApplicationModelProviderContext context); + + /// + /// Executed for the second pass of building instances. See . + /// + /// The . + void OnProvidersExecuted(PageApplicationModelProviderContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageConvention.cs new file mode 100644 index 0000000000..3b23349536 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageConvention.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.ApplicationModels +{ + /// + /// Common interface for route and application model conventions that apply to Razor Pages. + /// + public interface IPageConvention + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageHandlerModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageHandlerModelConvention.cs new file mode 100644 index 0000000000..df2b0c7a69 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageHandlerModelConvention.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.ApplicationModels +{ + /// + /// Allows customization of the . + /// + public interface IPageHandlerModelConvention : IPageConvention + { + /// + /// Called to apply the convention to the . + /// + /// The . + void Apply(PageHandlerModel model); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs new file mode 100644 index 0000000000..693e11cf3a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.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.ApplicationModels +{ + /// + /// Allows customization of the . + /// + public interface IPageRouteModelConvention : IPageConvention + { + /// + /// Called to apply the convention to the . + /// + /// The . + void Apply(PageRouteModel model); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelProvider.cs new file mode 100644 index 0000000000..300d8ecf9c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelProvider.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. + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// Builds or modifies an for Razor Page routing. + /// + public interface IPageRouteModelProvider + { + /// + /// Gets the order value for determining the order of execution of providers. Providers execute in + /// ascending numeric value of the property. + /// + /// + /// + /// Providers are executed in an ordering determined by an ascending sort of the property. + /// A provider with a lower numeric value of will have its + /// called before that of a provider with a higher numeric value of + /// . The method is called in the reverse ordering after + /// all calls to . A provider with a lower numeric value of + /// will have its method called after that of a provider + /// with a higher numeric value of . + /// + /// + /// If two providers have the same numeric value of , then their relative execution order + /// is undefined. + /// + /// + int Order { get; } + + /// + /// Executed for the first pass of building instances. See . + /// + /// The . + void OnProvidersExecuting(PageRouteModelProviderContext context); + + /// + /// Executed for the second pass of building instances. See . + /// + /// The . + void OnProvidersExecuted(PageRouteModelProviderContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs new file mode 100644 index 0000000000..5077e26a27 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs @@ -0,0 +1,158 @@ +// Copyright (c) .NET 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.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// Application model component for RazorPages. + /// + public class PageApplicationModel + { + /// + /// Initializes a new instance of . + /// + public PageApplicationModel( + PageActionDescriptor actionDescriptor, + TypeInfo handlerType, + IReadOnlyList handlerAttributes) + : this(actionDescriptor, handlerType, handlerType, handlerAttributes) + { + } + + /// + /// Initializes a new instance of . + /// + public PageApplicationModel( + PageActionDescriptor actionDescriptor, + TypeInfo declaredModelType, + TypeInfo handlerType, + IReadOnlyList handlerAttributes) + { + ActionDescriptor = actionDescriptor ?? throw new ArgumentNullException(nameof(actionDescriptor)); + DeclaredModelType = declaredModelType; + HandlerType = handlerType; + + Filters = new List(); + Properties = new CopyOnWriteDictionary( + actionDescriptor.Properties, + EqualityComparer.Default); + HandlerMethods = new List(); + HandlerProperties = new List(); + HandlerTypeAttributes = handlerAttributes; + } + + /// + /// A copy constructor for . + /// + /// The to copy from. + public PageApplicationModel(PageApplicationModel other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + ActionDescriptor = other.ActionDescriptor; + HandlerType = other.HandlerType; + PageType = other.PageType; + ModelType = other.ModelType; + + Filters = new List(other.Filters); + Properties = new Dictionary(other.Properties); + + HandlerMethods = new List(other.HandlerMethods.Select(m => new PageHandlerModel(m))); + HandlerProperties = new List(other.HandlerProperties.Select(p => new PagePropertyModel(p))); + HandlerTypeAttributes = other.HandlerTypeAttributes; + } + + /// + /// Gets the . + /// + public PageActionDescriptor ActionDescriptor { get; } + + /// + /// Gets the application root relative path for the page. + /// + public string RelativePath => ActionDescriptor.RelativePath; + + /// + /// Gets the path relative to the base path for page discovery. + /// + /// This value is the path of the file without extension, relative to the pages root directory. + /// e.g. the for the file /Pages/Catalog/Antiques.cshtml is /Catalog/Antiques + /// + /// + /// In an area, this value is the path of the file without extension, relative to the pages root directory for the specified area. + /// e.g. the for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage/Accounts. + /// + /// + public string ViewEnginePath => ActionDescriptor.ViewEnginePath; + + /// + /// Gets the area name. + /// + public string AreaName => ActionDescriptor.AreaName; + + /// + /// Gets the route template for the page. + /// + public string RouteTemplate => ActionDescriptor.AttributeRouteInfo?.Template; + + /// + /// Gets the applicable instances. + /// + public IList Filters { get; } + + /// + /// Stores arbitrary metadata properties associated with the . + /// + public IDictionary Properties { get; } + + /// + /// Gets or sets the of the Razor page. + /// + public TypeInfo PageType { get; set; } + + /// + /// Gets the declared model of the model for the page. + /// Typically this will be the type specified by the @model directive + /// in the razor page. + /// + public TypeInfo DeclaredModelType { get; } + + /// + /// Gets or sets the runtime model of the model for the razor page. + /// This is the that will be used at runtime to instantiate and populate + /// the model property of the page. + /// + public TypeInfo ModelType { get; set; } + + /// + /// Gets the of the handler. + /// + public TypeInfo HandlerType { get; } + + /// + /// Gets the sequence of attributes declared on . + /// + public IReadOnlyList HandlerTypeAttributes { get; } + + /// + /// Gets the sequence of instances. + /// + public IList HandlerMethods { get; } + + /// + /// Gets the sequence of instances on . + /// + public IList HandlerProperties { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModelProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModelProviderContext.cs new file mode 100644 index 0000000000..cccb6824e5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModelProviderContext.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.Reflection; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// A context object for . + /// + public class PageApplicationModelProviderContext + { + public PageApplicationModelProviderContext(PageActionDescriptor descriptor, TypeInfo pageTypeInfo) + { + ActionDescriptor = descriptor; + PageType = pageTypeInfo; + } + + /// + /// Gets the . + /// + public PageActionDescriptor ActionDescriptor { get; } + + /// + /// Gets the page . + /// + public TypeInfo PageType { get; } + + /// + /// Gets or sets the . + /// + public PageApplicationModel PageApplicationModel { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageConventionCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageConventionCollection.cs new file mode 100644 index 0000000000..ba38593f10 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageConventionCollection.cs @@ -0,0 +1,423 @@ +// Copyright (c) .NET 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 Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class PageConventionCollection : Collection + { + /// + /// Initializes a new instance of the class that is empty. + /// + public PageConventionCollection() + { + } + + /// + /// Initializes a new instance of the class + /// as a wrapper for the specified list. + /// + /// The list that is wrapped by the new collection. + public PageConventionCollection(IList conventions) + : base(conventions) + { + } + + /// + /// Creates and adds an that invokes an action on the + /// for the page with the specified name. + /// + /// The name of the page e.g. /Users/List + /// The . + /// The added . + public IPageApplicationModelConvention AddPageApplicationModelConvention( + string pageName, + Action action) + { + EnsureValidPageName(pageName); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new PageApplicationModelConvention(pageName, action)); + } + + /// + /// Creates and adds an that invokes an action on the + /// for the page with the specified name located in the specified area. + /// + /// The name of area. + /// + /// The page name e.g. /Users/List + /// + /// The page name is the path of the file without extension, relative to the pages root directory for the specified area. + /// e.g. the page name for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage/Accounts. + /// + /// + /// The . + /// The added . + public IPageApplicationModelConvention AddAreaPageApplicationModelConvention( + string areaName, + string pageName, + Action action) + { + if (string.IsNullOrEmpty(areaName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); + } + + EnsureValidPageName(pageName); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new PageApplicationModelConvention(areaName, pageName, action)); + } + + /// + /// Creates and adds an that invokes an action on + /// instances for all page under the specified folder. + /// + /// The path of the folder relative to the Razor Pages root. e.g. /Users/ + /// The . + /// The added . + public IPageApplicationModelConvention AddFolderApplicationModelConvention(string folderPath, Action action) + { + EnsureValidFolderPath(folderPath); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new FolderApplicationModelConvention(folderPath, action)); + } + + /// + /// Creates and adds an that invokes an action on + /// instances for all pages under the specified area folder. + /// + /// The name of area. + /// + /// The folder path e.g. /Manage/ + /// + /// The folder path is the path of the folder, relative to the pages root directory for the specified area. + /// e.g. the folder path for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage. + /// + /// + /// The . + /// The added . + public IPageApplicationModelConvention AddAreaFolderApplicationModelConvention( + string areaName, + string folderPath, + Action action) + { + if (string.IsNullOrEmpty(areaName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); + } + + EnsureValidFolderPath(folderPath); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new FolderApplicationModelConvention(areaName, folderPath, action)); + } + + /// + /// Creates and adds an that invokes an action on the + /// for the page with the specified name. + /// + /// The name of the page e.g. /Users/List + /// The . + /// The added . + public IPageRouteModelConvention AddPageRouteModelConvention(string pageName, Action action) + { + EnsureValidPageName(pageName); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new PageRouteModelConvention(pageName, action)); + } + + /// + /// Creates and adds an that invokes an action on the + /// for the page with the specified name located in the specified area. + /// + /// The area name. + /// + /// The page name e.g. /Users/List + /// + /// The page name is the path of the file without extension, relative to the pages root directory for the specified area. + /// e.g. the page name for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage/Accounts. + /// + /// + /// The . + /// The added . + public IPageRouteModelConvention AddAreaPageRouteModelConvention(string areaName, string pageName, Action action) + { + if (string.IsNullOrEmpty(areaName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); + } + + EnsureValidPageName(pageName); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new PageRouteModelConvention(areaName, pageName, action)); + } + + /// + /// Creates and adds an that invokes an action on + /// instances for all page under the specified folder. + /// + /// The path of the folder relative to the Razor Pages root. e.g. /Users/ + /// The . + /// The added . + public IPageRouteModelConvention AddFolderRouteModelConvention(string folderPath, Action action) + { + EnsureValidFolderPath(folderPath); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new FolderRouteModelConvention(folderPath, action)); + } + + /// + /// Creates and adds an that invokes an action on + /// instances for all page under the specified area folder. + /// + /// The area name. + /// + /// The folder path e.g. /Manage/ + /// + /// The folder path is the path of the folder, relative to the pages root directory for the specified area. + /// e.g. the folder path for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage. + /// + /// + /// The . + /// The added . + public IPageRouteModelConvention AddAreaFolderRouteModelConvention(string areaName, string folderPath, Action action) + { + if (string.IsNullOrEmpty(areaName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); + } + + EnsureValidFolderPath(folderPath); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new FolderRouteModelConvention(areaName, folderPath, action)); + } + + /// + /// Removes all instances of the specified type. + /// + /// The type to remove. + public void RemoveType() where TPageConvention : IPageConvention + { + RemoveType(typeof(TPageConvention)); + } + + /// + /// Removes all instances of the specified type. + /// + /// The type to remove. + public void RemoveType(Type pageConventionType) + { + for (var i = Count - 1; i >= 0; i--) + { + var pageConvention = this[i]; + if (pageConvention.GetType() == pageConventionType) + { + RemoveAt(i); + } + } + } + + // Internal for unit testing + internal static void EnsureValidPageName(string pageName) + { + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + if (pageName[0] != '/' || pageName.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException(Resources.FormatInvalidValidPageName(pageName), nameof(pageName)); + } + } + + // Internal for unit testing + internal static void EnsureValidFolderPath(string folderPath) + { + if (string.IsNullOrEmpty(folderPath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); + } + + if (folderPath[0] != '/') + { + throw new ArgumentException(Resources.PathMustBeRootRelativePath, nameof(folderPath)); + } + } + + private TConvention Add(TConvention convention) where TConvention : IPageConvention + { + base.Add(convention); + return convention; + } + + private class PageRouteModelConvention : IPageRouteModelConvention + { + private readonly string _areaName; + private readonly string _path; + private readonly Action _action; + + public PageRouteModelConvention(string path, Action action) + : this(null, path, action) + { + } + + public PageRouteModelConvention(string areaName, string path, Action action) + { + _areaName = areaName; + _path = path; + _action = action; + } + + public void Apply(PageRouteModel model) + { + if (string.Equals(_areaName, model.AreaName, StringComparison.OrdinalIgnoreCase) && + string.Equals(model.ViewEnginePath, _path, StringComparison.OrdinalIgnoreCase)) + { + _action(model); + } + } + } + + private class FolderRouteModelConvention : IPageRouteModelConvention + { + private readonly string _areaName; + private readonly string _folderPath; + private readonly Action _action; + + public FolderRouteModelConvention(string folderPath, Action action) + : this(null, folderPath, action) + { + } + + public FolderRouteModelConvention(string areaName, string folderPath, Action action) + { + _areaName = areaName; + _folderPath = folderPath.TrimEnd('/'); + _action = action; + } + + public void Apply(PageRouteModel model) + { + if (string.Equals(_areaName, model.AreaName, StringComparison.OrdinalIgnoreCase) && + PathBelongsToFolder(_folderPath, model.ViewEnginePath)) + { + _action(model); + } + } + } + + private class PageApplicationModelConvention : IPageApplicationModelConvention + { + private readonly string _areaName; + private readonly string _path; + private readonly Action _action; + + public PageApplicationModelConvention(string path, Action action) + : this(null, path, action) + { + } + + public PageApplicationModelConvention(string areaName, string path, Action action) + { + _areaName = areaName; + _path = path; + _action = action; + } + + public void Apply(PageApplicationModel model) + { + if (string.Equals(model.ViewEnginePath, _path, StringComparison.OrdinalIgnoreCase) && + string.Equals(model.AreaName, _areaName, StringComparison.OrdinalIgnoreCase)) + { + _action(model); + } + } + } + + private class FolderApplicationModelConvention : IPageApplicationModelConvention + { + private readonly string _areaName; + private readonly string _folderPath; + private readonly Action _action; + + public FolderApplicationModelConvention(string folderPath, Action action) + : this(null, folderPath, action) + { + } + + public FolderApplicationModelConvention(string areaName, string folderPath, Action action) + { + _areaName = areaName; + _folderPath = folderPath.TrimEnd('/'); + _action = action; + } + + public void Apply(PageApplicationModel model) + { + if (string.Equals(_areaName, model.AreaName, StringComparison.OrdinalIgnoreCase) && + PathBelongsToFolder(_folderPath, model.ViewEnginePath)) + { + _action(model); + } + } + } + + // Internal for unit testing + internal static bool PathBelongsToFolder(string folderPath, string viewEnginePath) + { + if (folderPath == "/") + { + // Root directory covers everything. + return true; + } + + return viewEnginePath.Length > folderPath.Length && + viewEnginePath.StartsWith(folderPath, StringComparison.OrdinalIgnoreCase) && + viewEnginePath[folderPath.Length] == '/'; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageHandlerModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageHandlerModel.cs new file mode 100644 index 0000000000..6923269c14 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageHandlerModel.cs @@ -0,0 +1,98 @@ +// Copyright (c) .NET 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.Linq; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// Represents a handler in a . + /// + [DebuggerDisplay("PageHandlerModel: Name={" + nameof(PageHandlerModel.Name) + "}")] + public class PageHandlerModel : ICommonModel + { + /// + /// Creates a new . + /// + /// The for the handler. + /// Any attributes annotated on the handler method. + public PageHandlerModel( + MethodInfo handlerMethod, + IReadOnlyList attributes) + { + MethodInfo = handlerMethod ?? throw new ArgumentNullException(nameof(handlerMethod)); + Attributes = attributes ?? throw new ArgumentNullException(nameof(attributes)); + + Parameters = new List(); + Properties = new Dictionary(); + } + + /// + /// Creates a new instance of from a given . + /// + /// The which needs to be copied. + public PageHandlerModel(PageHandlerModel other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + MethodInfo = other.MethodInfo; + HandlerName = other.HandlerName; + HttpMethod = other.HttpMethod; + Name = other.Name; + + Page = other.Page; + + // These are just metadata, safe to create new collections + Attributes = new List(other.Attributes); + Properties = new Dictionary(other.Properties); + + // Make a deep copy of other 'model' types. + Parameters = new List(other.Parameters.Select(p => new PageParameterModel(p) { Handler = this })); + } + + /// + /// Gets the for the handler. + /// + public MethodInfo MethodInfo { get; } + + /// + /// Gets or sets the HTTP method supported by this handler. + /// + public string HttpMethod { get; set; } + + /// + /// Gets or sets the handler method name. + /// + public string HandlerName { get; set; } + + /// + /// Gets or sets a descriptive name for the handler. + /// + public string Name { get; set; } + + /// + /// Gets the sequence of instances. + /// + public IList Parameters { get; } + + /// + /// Gets or sets the . + /// + public PageApplicationModel Page { get; set; } + + /// + public IReadOnlyList Attributes { get; } + + /// + public IDictionary Properties { get; } + + MemberInfo ICommonModel.MemberInfo => MethodInfo; + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageParameterModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageParameterModel.cs new file mode 100644 index 0000000000..0b4851b1ee --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageParameterModel.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.Collections.Generic; +using System.Diagnostics; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + [DebuggerDisplay("PageParameterModel: Name={ParameterName}")] + public class PageParameterModel : ParameterModelBase, ICommonModel, IBindingModel + { + public PageParameterModel( + ParameterInfo parameterInfo, + IReadOnlyList attributes) + : base(parameterInfo?.ParameterType, attributes) + { + if (parameterInfo == null) + { + throw new ArgumentNullException(nameof(parameterInfo)); + } + + if (attributes == null) + { + throw new ArgumentNullException(nameof(attributes)); + } + + ParameterInfo = parameterInfo; + } + + public PageParameterModel(PageParameterModel other) + : base(other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + Handler = other.Handler; + ParameterInfo = other.ParameterInfo; + } + + public PageHandlerModel Handler { get; set; } + + MemberInfo ICommonModel.MemberInfo => ParameterInfo.Member; + + public ParameterInfo ParameterInfo { get; } + + public string ParameterName + { + get => Name; + set => Name = value; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PagePropertyModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PagePropertyModel.cs new file mode 100644 index 0000000000..5af225dee6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PagePropertyModel.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; +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// Represents a property in a . + /// + [DebuggerDisplay("PagePropertyModel: Name={PropertyName}")] + public class PagePropertyModel : ParameterModelBase, ICommonModel + { + /// + /// Creates a new instance of . + /// + /// The for the underlying property. + /// Any attributes which are annotated on the property. + public PagePropertyModel( + PropertyInfo propertyInfo, + IReadOnlyList attributes) + : base(propertyInfo?.PropertyType, attributes) + { + PropertyInfo = propertyInfo ?? throw new ArgumentNullException(nameof(propertyInfo)); + } + + /// + /// Creates a new instance of from a given . + /// + /// The which needs to be copied. + public PagePropertyModel(PagePropertyModel other) + : base(other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + Page = other.Page; + BindingInfo = BindingInfo == null ? null : new BindingInfo(other.BindingInfo); + PropertyInfo = other.PropertyInfo; + } + + /// + /// Gets or sets the this is associated with. + /// + public PageApplicationModel Page { get; set; } + + MemberInfo ICommonModel.MemberInfo => PropertyInfo; + + public PropertyInfo PropertyInfo { get; } + + public string PropertyName + { + get => Name; + set => Name = value; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModel.cs new file mode 100644 index 0000000000..8c94ae18a7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModel.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; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// A model component for routing RazorPages. + /// + public class PageRouteModel + { + /// + /// Initializes a new instance of . + /// + /// The application relative path of the page. + /// The path relative to the base path for page discovery. + public PageRouteModel(string relativePath, string viewEnginePath) + : this(relativePath, viewEnginePath, areaName: null) + { + } + + /// + /// Initializes a new instance of . + /// + /// The application relative path of the page. + /// The path relative to the base path for page discovery. + /// The area name. + public PageRouteModel(string relativePath, string viewEnginePath, string areaName) + { + RelativePath = relativePath ?? throw new ArgumentNullException(nameof(relativePath)); + ViewEnginePath = viewEnginePath ?? throw new ArgumentNullException(nameof(viewEnginePath)); + AreaName = areaName; + + Properties = new Dictionary(); + Selectors = new List(); + RouteValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + /// + /// A copy constructor for . + /// + /// The to copy from. + public PageRouteModel(PageRouteModel other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + RelativePath = other.RelativePath; + ViewEnginePath = other.ViewEnginePath; + AreaName = other.AreaName; + + Properties = new Dictionary(other.Properties); + Selectors = new List(other.Selectors.Select(m => new SelectorModel(m))); + RouteValues = new Dictionary(other.RouteValues, StringComparer.OrdinalIgnoreCase); + } + + /// + /// Gets the application root relative path for the page. + /// + public string RelativePath { get; } + + /// + /// Gets the path relative to the base path for page discovery. + /// + /// This value is the path of the file without extension, relative to the pages root directory. + /// e.g. the for the file /Pages/Catalog/Antiques.cshtml is /Catalog/Antiques + /// + /// + /// In an area, this value is the path of the file without extension, relative to the pages root directory for the specified area. + /// e.g. the for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage/Accounts. + /// + /// + public string ViewEnginePath { get; } + + /// + /// Gets the area name. Will be null for non-area pages. + /// + public string AreaName { get; } + + /// + /// Stores arbitrary metadata properties associated with the . + /// + public IDictionary Properties { get; } + + /// + /// Gets the instances. + /// + public IList Selectors { get; } + + /// + /// Gets a collection of route values that must be present in the + /// for the corresponding page to be selected. + /// + /// + /// + /// The value of is considered an implicit route value corresponding + /// to the key page. + /// + /// + /// The value of is considered an implicit route value corresponding + /// to the key area when is not null. + /// + /// + /// These entries will be implicitly added to + /// when the action descriptor is created, but will not be visible in . + /// + /// + public IDictionary RouteValues { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModelProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModelProviderContext.cs new file mode 100644 index 0000000000..966eb3876b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModelProviderContext.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.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// A context object for . + /// + public class PageRouteModelProviderContext + { + /// + /// Gets the instances. + /// + public IList RouteModels { get; } = new List(); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs new file mode 100644 index 0000000000..cd81ea8888 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.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.Collections.Generic; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + /// + /// A for a compiled Razor page. + /// + public class CompiledPageActionDescriptor : PageActionDescriptor + { + /// + /// Initializes an empty . + /// + public CompiledPageActionDescriptor() + { + } + + /// + /// Initializes a new instance of + /// from the specified instance. + /// + /// The . + public CompiledPageActionDescriptor(PageActionDescriptor actionDescriptor) + : base(actionDescriptor) + { + } + + /// + /// Gets the list of handler methods for the page. + /// + public IList HandlerMethods { get; set; } + + /// + /// Gets or sets the of the type that defines handler methods for the page. This can be + /// the same as and if the page does not have an + /// explicit model type defined. + /// + public TypeInfo HandlerTypeInfo { get; set; } + + /// + /// Gets or sets the declared model of the model for the page. + /// Typically this will be the type specified by the @model directive + /// in the razor page. + /// + public TypeInfo DeclaredModelTypeInfo { get; set; } + + /// + /// Gets or sets the runtime model of the model for the razor page. + /// This is the that will be used at runtime to instantiate and populate + /// the model property of the page. + /// + public TypeInfo ModelTypeInfo { get; set; } + + /// + /// Gets or sets the of the page. + /// + public TypeInfo PageTypeInfo { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs new file mode 100644 index 0000000000..6340d87005 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.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 Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extensions methods for configuring Razor Pages via an . + /// + public static class MvcRazorPagesMvcBuilderExtensions + { + /// + /// Configures a set of for the application. + /// + /// The . + /// An action to configure the . + /// The . + public static IMvcBuilder AddRazorPagesOptions( + 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; + } + + /// + /// Configures Razor Pages to use the specified . + /// + /// The . + /// The application relative path to use as the root directory. + /// The . + public static IMvcBuilder WithRazorPagesRoot(this IMvcBuilder builder, string rootDirectory) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (string.IsNullOrEmpty(rootDirectory)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(rootDirectory)); + } + + if (rootDirectory[0] != '/') + { + throw new ArgumentException(Resources.PathMustBeRootRelativePath, nameof(rootDirectory)); + } + + builder.Services.Configure(options => options.RootDirectory = rootDirectory); + return builder; + } + + /// + /// Configures Razor Pages to be rooted at the content root (). + /// + /// The . + /// The . + public static IMvcBuilder WithRazorPagesAtContentRoot(this IMvcBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Services.Configure(options => options.RootDirectory = "/"); + return builder; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs new file mode 100644 index 0000000000..35a6e75a45 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs @@ -0,0 +1,133 @@ +// Copyright (c) .NET 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.Abstractions; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Infrastructure; +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.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class MvcRazorPagesMvcCoreBuilderExtensions + { + public static IMvcCoreBuilder AddRazorPages(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.AddRazorViewEngine(); + + AddServices(builder.Services); + + return builder; + } + + public static IMvcCoreBuilder AddRazorPages( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + builder.AddRazorViewEngine(); + + AddServices(builder.Services); + + builder.Services.Configure(setupAction); + + return builder; + } + + /// + /// Configures Razor Pages to use the specified . + /// + /// The . + /// The application relative path to use as the root directory. + /// + public static IMvcCoreBuilder WithRazorPagesRoot(this IMvcCoreBuilder builder, string rootDirectory) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (string.IsNullOrEmpty(rootDirectory)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(rootDirectory)); + } + + builder.Services.Configure(options => options.RootDirectory = rootDirectory); + return builder; + } + + // Internal for testing. + internal static void AddServices(IServiceCollection services) + { + // Options + services.TryAddEnumerable( + ServiceDescriptor.Transient, RazorPagesRazorViewEngineOptionsSetup>()); + services.TryAddEnumerable( + ServiceDescriptor.Transient, RazorPagesOptionsConfigureCompatibilityOptions>()); + + // Action description and invocation + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + services.TryAddSingleton(); + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + + // Page and Page model creation and activation + services.TryAddSingleton(); + services.TryAddSingleton(); + + services.TryAddSingleton(); + services.TryAddSingleton(); + + services.TryAddSingleton(); + services.TryAddSingleton(); + + // Page model binding +#pragma warning disable CS0618 // Type or member is obsolete + services.TryAddSingleton(); +#pragma warning restore CS0618 // Type or member is obsolete + + // Action executors + services.TryAddSingleton(); + + services.TryAddTransient(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/PageConventionCollectionExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/PageConventionCollectionExtensions.cs new file mode 100644 index 0000000000..577c8b5ed3 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/PageConventionCollectionExtensions.cs @@ -0,0 +1,504 @@ +// Copyright (c) .NET 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; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extensions for . + /// + public static class PageConventionCollectionExtensions + { + /// + /// Configures the specified to apply filters to all Razor Pages. + /// + /// The to configure. + /// The factory to create filters. + /// + public static IPageApplicationModelConvention ConfigureFilter( + this PageConventionCollection conventions, + Func factory) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + return conventions.AddFolderApplicationModelConvention("/", model => model.Filters.Add(factory(model))); + } + + /// + /// Configures the specified to apply to all Razor Pages. + /// + /// The to configure. + /// The to add. + /// The . + public static PageConventionCollection ConfigureFilter(this PageConventionCollection conventions, IFilterMetadata filter) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (filter == null) + { + throw new ArgumentNullException(nameof(filter)); + } + + conventions.AddFolderApplicationModelConvention("/", model => model.Filters.Add(filter)); + return conventions; + } + + /// + /// Adds a to the page with the specified name. + /// + /// The to configure. + /// The page name. + /// The . + public static PageConventionCollection AllowAnonymousToPage(this PageConventionCollection conventions, string pageName) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + var anonymousFilter = new AllowAnonymousFilter(); + conventions.AddPageApplicationModelConvention(pageName, model => model.Filters.Add(anonymousFilter)); + return conventions; + } + + /// + /// Adds a to the page with the specified name located in the specified area. + /// + /// The to configure. + /// The area name. + /// + /// The page name e.g. /Users/List + /// + /// The page name is the path of the file without extension, relative to the pages root directory for the specified area. + /// e.g. the page name for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage/Accounts. + /// + /// + /// The . + public static PageConventionCollection AllowAnonymousToAreaPage( + this PageConventionCollection conventions, + string areaName, + string pageName) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(areaName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); + } + + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + var anonymousFilter = new AllowAnonymousFilter(); + conventions.AddAreaPageApplicationModelConvention(areaName, pageName, model => model.Filters.Add(anonymousFilter)); + return conventions; + } + + /// + /// Adds a to all pages under the specified folder. + /// + /// The to configure. + /// The folder path. + /// The . + public static PageConventionCollection AllowAnonymousToFolder(this PageConventionCollection conventions, string folderPath) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(folderPath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); + } + + var anonymousFilter = new AllowAnonymousFilter(); + conventions.AddFolderApplicationModelConvention(folderPath, model => model.Filters.Add(anonymousFilter)); + return conventions; + } + + /// + /// Adds the specified to . + /// The added convention will apply to all handler properties and parameters on handler methods. + /// + /// The to configure. + /// The to apply. + /// The . + public static PageConventionCollection Add(this PageConventionCollection conventions, IParameterModelBaseConvention convention) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (convention == null) + { + throw new ArgumentNullException(nameof(convention)); + } + + var adapter = new ParameterModelBaseConventionAdapter(convention); + conventions.Add(adapter); + return conventions; + } + + /// + /// Adds a to all pages under the specified area folder. + /// + /// The to configure. + /// The area name. + /// + /// The folder path e.g. /Manage/ + /// + /// The folder path is the path of the folder, relative to the pages root directory for the specified area. + /// e.g. the folder path for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage. + /// + ///. + /// The . + public static PageConventionCollection AllowAnonymousToAreaFolder( + this PageConventionCollection conventions, + string areaName, + string folderPath) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(areaName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); + } + + if (string.IsNullOrEmpty(folderPath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); + } + + var anonymousFilter = new AllowAnonymousFilter(); + conventions.AddAreaFolderApplicationModelConvention(areaName, folderPath, model => model.Filters.Add(anonymousFilter)); + return conventions; + } + + /// + /// Adds a with the specified policy to the page with the specified name. + /// + /// The to configure. + /// The page name. + /// The authorization policy. + /// The . + public static PageConventionCollection AuthorizePage(this PageConventionCollection conventions, string pageName, string policy) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + var authorizeFilter = new AuthorizeFilter(policy); + conventions.AddPageApplicationModelConvention(pageName, model => model.Filters.Add(authorizeFilter)); + return conventions; + } + + /// + /// Adds a to the page with the specified name. + /// + /// The to configure. + /// The page name. + /// The . + public static PageConventionCollection AuthorizePage(this PageConventionCollection conventions, string pageName) => + AuthorizePage(conventions, pageName, policy: string.Empty); + + /// + /// Adds a with default policy to the page with the specified name. + /// + /// The to configure. + /// The area name. + /// + /// The page name e.g. /Users/List + /// + /// The page name is the path of the file without extension, relative to the pages root directory for the specified area. + /// e.g. the page name for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage/Accounts. + /// + /// + /// The . + public static PageConventionCollection AuthorizeAreaPage(this PageConventionCollection conventions, string areaName, string pageName) + => AuthorizeAreaPage(conventions, areaName, pageName, policy: string.Empty); + + /// + /// Adds a with the specified policy to the page with the specified name. + /// + /// The to configure. + /// The area name. + /// + /// The page name e.g. /Users/List + /// + /// The page name is the path of the file without extension, relative to the pages root directory for the specified area. + /// e.g. the page name for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage/Accounts. + /// + /// + /// The authorization policy. + /// The . + public static PageConventionCollection AuthorizeAreaPage( + this PageConventionCollection conventions, + string areaName, + string pageName, + string policy) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(areaName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); + } + + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + var authorizeFilter = new AuthorizeFilter(policy); + conventions.AddAreaPageApplicationModelConvention(areaName, pageName, model => model.Filters.Add(authorizeFilter)); + return conventions; + } + + /// + /// Adds a with the specified policy to all pages under the specified folder. + /// + /// The to configure. + /// The folder path. + /// The authorization policy. + /// The . + public static PageConventionCollection AuthorizeFolder(this PageConventionCollection conventions, string folderPath, string policy) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(folderPath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); + } + + var authorizeFilter = new AuthorizeFilter(policy); + conventions.AddFolderApplicationModelConvention(folderPath, model => model.Filters.Add(authorizeFilter)); + return conventions; + } + + /// + /// Adds a to all pages under the specified folder. + /// + /// The to configure. + /// The folder path. + /// The . + public static PageConventionCollection AuthorizeFolder(this PageConventionCollection conventions, string folderPath) => + AuthorizeFolder(conventions, folderPath, policy: string.Empty); + + /// + /// Adds a with the default policy to all pages under the specified folder. + /// + /// The to configure. + /// The area name. + /// + /// The folder path e.g. /Manage/ + /// + /// The folder path is the path of the folder, relative to the pages root directory for the specified area. + /// e.g. the folder path for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage. + /// + /// + /// The . + public static PageConventionCollection AuthorizeAreaFolder(this PageConventionCollection conventions, string areaName, string folderPath) + => AuthorizeAreaFolder(conventions, areaName, folderPath, policy: string.Empty); + + /// + /// Adds a with the specified policy to all pages under the specified folder. + /// + /// The to configure. + /// The area name. + /// + /// The folder path e.g. /Manage/ + /// + /// The folder path is the path of the folder, relative to the pages root directory for the specified area. + /// e.g. the folder path for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage. + /// + /// + /// The authorization policy. + /// The . + public static PageConventionCollection AuthorizeAreaFolder( + this PageConventionCollection conventions, + string areaName, + string folderPath, + string policy) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(areaName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); + } + + if (string.IsNullOrEmpty(folderPath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); + } + + var authorizeFilter = new AuthorizeFilter(policy); + conventions.AddAreaFolderApplicationModelConvention(areaName, folderPath, model => model.Filters.Add(authorizeFilter)); + return conventions; + } + + /// + /// Adds the specified to the page at the specified . + /// + /// The page can be routed via in addition to the default set of path based routes. + /// All links generated for this page will use the specified route. + /// + /// + /// The . + /// The page name. + /// The route to associate with the page. + /// The . + public static PageConventionCollection AddPageRoute(this PageConventionCollection conventions, string pageName, string route) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + if (route == null) + { + throw new ArgumentNullException(nameof(route)); + } + + conventions.AddPageRouteModelConvention(pageName, AddPageRouteThunk(route)); + + return conventions; + } + + /// + /// Adds the specified to the page at the specified located in the specified + /// area. + /// + /// The page can be routed via in addition to the default set of path based routes. + /// All links generated for this page will use the specified route. + /// + /// + /// The . + /// The area name. + /// + /// The page name e.g. /Users/List + /// + /// The page name is the path of the file without extension, relative to the pages root directory for the specified area. + /// e.g. the page name for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage/Accounts. + /// + /// + /// The route to associate with the page. + /// The . + public static PageConventionCollection AddAreaPageRoute( + this PageConventionCollection conventions, + string areaName, + string pageName, + string route) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(areaName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); + } + + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + if (route == null) + { + throw new ArgumentNullException(nameof(route)); + } + + conventions.AddAreaPageRouteModelConvention(areaName, pageName, AddPageRouteThunk(route)); + + return conventions; + } + + private static Action AddPageRouteThunk(string route) + { + return model => + { + // Use the route specified in MapPageRoute for outbound routing. + foreach (var selector in model.Selectors) + { + selector.AttributeRouteModel.SuppressLinkGeneration = true; + } + + model.Selectors.Add(new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel + { + Template = route, + } + }); + }; + } + + private class ParameterModelBaseConventionAdapter : IPageConvention, IParameterModelBaseConvention + { + private readonly IParameterModelBaseConvention _convention; + + public ParameterModelBaseConventionAdapter(IParameterModelBaseConvention convention) + { + _convention = convention; + } + + public void Apply(ParameterModelBase parameter) + { + _convention.Apply(parameter); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IAsyncPageFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IAsyncPageFilter.cs new file mode 100644 index 0000000000..1c6a0503df --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IAsyncPageFilter.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.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that asynchronously surrounds execution of a page handler method. This filter is executed only when + /// decorated on a handler's type and not on individual handler methods. + /// + public interface IAsyncPageFilter : IFilterMetadata + { + /// + /// Called asynchronously after the handler method has been selected, but before model binding occurs. + /// + /// The . + /// A that on completion indicates the filter has executed. + Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context); + + /// + /// Called asynchronously before the handler method is invoked, after model binding is complete. + /// + /// The . + /// + /// The . Invoked to execute the next page filter or the handler method itself. + /// + /// A that on completion indicates the filter has executed. + Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IPageFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IPageFilter.cs new file mode 100644 index 0000000000..6fde59efb1 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IPageFilter.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. + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A filter that surrounds execution of a page handler method. This filter is executed only when decorated on a + /// handler's type and not on individual handler methods. + /// + public interface IPageFilter : IFilterMetadata + { + /// + /// Called after a handler method has been selected, but before model binding occurs. + /// + /// The . + void OnPageHandlerSelected(PageHandlerSelectedContext context); + + /// + /// Called before the handler method executes, after model binding is complete. + /// + /// The . + void OnPageHandlerExecuting(PageHandlerExecutingContext context); + + /// + /// Called after the handler method executes, before the action result executes. + /// + /// The . + void OnPageHandlerExecuted(PageHandlerExecutedContext context); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutedContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutedContext.cs new file mode 100644 index 0000000000..5597fdc92e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutedContext.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 System.Collections.Generic; +using System.Runtime.ExceptionServices; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for page filters, used specifically in + /// and + /// . + /// + public class PageHandlerExecutedContext : FilterContext + { + private Exception _exception; + private ExceptionDispatchInfo _exceptionDispatchInfo; + + /// + /// Creates a new instance of . + /// + /// The associated with the current request. + /// The set of filters associated with the page. + /// The handler method to be invoked, may be null. + /// The handler instance associated with the page. + public PageHandlerExecutedContext( + PageContext pageContext, + IList filters, + HandlerMethodDescriptor handlerMethod, + object handlerInstance) + : base(pageContext, filters) + { + if (handlerInstance == null) + { + throw new ArgumentNullException(nameof(handlerInstance)); + } + + HandlerMethod = handlerMethod; + HandlerInstance = handlerInstance; + } + + /// + /// Gets the descriptor associated with the current page. + /// + public new virtual CompiledPageActionDescriptor ActionDescriptor => + (CompiledPageActionDescriptor)base.ActionDescriptor; + + /// + /// Gets or sets an indication that an page filter short-circuited the action and the page filter pipeline. + /// + public virtual bool Canceled { get; set; } + + /// + /// Gets the handler instance containing the handler method. + /// + public virtual object HandlerInstance { get; } + + /// + /// Gets the descriptor for the handler method that was invoked. + /// + public virtual HandlerMethodDescriptor HandlerMethod { get; } + + /// + /// Gets or sets the caught while executing the action or action filters, if + /// any. + /// + public virtual Exception Exception + { + get + { + if (_exception == null && _exceptionDispatchInfo != null) + { + return _exceptionDispatchInfo.SourceException; + } + else + { + return _exception; + } + } + + set + { + _exceptionDispatchInfo = null; + _exception = value; + } + } + + /// + /// Gets or sets the for the + /// , if an was caught and this information captured. + /// + public virtual ExceptionDispatchInfo ExceptionDispatchInfo + { + get => _exceptionDispatchInfo; + + set + { + _exception = null; + _exceptionDispatchInfo = value; + } + } + + /// + /// Gets or sets an indication that the has been handled. + /// + public virtual bool ExceptionHandled { get; set; } + + /// + /// Gets or sets the . + /// + public virtual IActionResult Result { get; set; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutingContext.cs new file mode 100644 index 0000000000..25b9b90b70 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutingContext.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for page filters, used specifically in + /// and + /// . + /// + public class PageHandlerExecutingContext : FilterContext + { + /// + /// Creates a new instance of . + /// + /// The associated with the current request. + /// The set of filters associated with the page. + /// The handler method to be invoked, may be null. + /// The arguments to provide to the handler method. + /// The handler instance associated with the page. + public PageHandlerExecutingContext( + PageContext pageContext, + IList filters, + HandlerMethodDescriptor handlerMethod, + IDictionary handlerArguments, + object handlerInstance) + : base(pageContext, filters) + { + if (handlerArguments == null) + { + throw new ArgumentNullException(nameof(handlerArguments)); + } + + if (handlerInstance == null) + { + throw new ArgumentNullException(nameof(handlerInstance)); + } + + HandlerMethod = handlerMethod; + HandlerArguments = handlerArguments; + HandlerInstance = handlerInstance; + } + + /// + /// Gets the descriptor associated with the current page. + /// + public new virtual CompiledPageActionDescriptor ActionDescriptor => + (CompiledPageActionDescriptor)base.ActionDescriptor; + + /// + /// Gets or sets the to execute. Setting to a non-null + /// value inside a page filter will short-circuit the page and any remaining page filters. + /// + public virtual IActionResult Result { get; set; } + + /// + /// Gets the arguments to pass when invoking the handler method. Keys are parameter names. + /// + public virtual IDictionary HandlerArguments { get; } + + /// + /// Gets the descriptor for the handler method about to be invoked. + /// + public virtual HandlerMethodDescriptor HandlerMethod { get; } + + /// + /// Gets the object instance containing the handler method. + /// + public virtual object HandlerInstance { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutionDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutionDelegate.cs new file mode 100644 index 0000000000..7bf9708b20 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutionDelegate.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 System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A delegate that asynchronously returns a indicating the page or the next + /// page filter has executed. + /// + /// + /// A that on completion returns an . + /// + public delegate Task PageHandlerExecutionDelegate(); +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerSelectedContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerSelectedContext.cs new file mode 100644 index 0000000000..8489ab3fe5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerSelectedContext.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; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Filters +{ + /// + /// A context for page filters, used specifically in + /// and + /// . + /// + public class PageHandlerSelectedContext : FilterContext + { + /// + /// Creates a new instance of . + /// + /// The associated with the current request. + /// The set of filters associated with the page. + /// The handler instance associated with the page. + public PageHandlerSelectedContext( + PageContext pageContext, + IList filters, + object handlerInstance) + : base(pageContext, filters) + { + if (handlerInstance == null) + { + throw new ArgumentNullException(nameof(handlerInstance)); + } + + HandlerInstance = handlerInstance; + } + + /// + /// Gets the descriptor associated with the current page. + /// + public new virtual CompiledPageActionDescriptor ActionDescriptor => + (CompiledPageActionDescriptor)base.ActionDescriptor; + + /// + /// Gets or sets the descriptor for the handler method about to be invoked. + /// + public virtual HandlerMethodDescriptor HandlerMethod { get; set; } + + /// + /// Gets the object instance containing the handler method. + /// + public virtual object HandlerInstance { get; } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageActivatorProvider.cs new file mode 100644 index 0000000000..80578d5f17 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageActivatorProvider.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 System; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + /// + /// Provides methods to create a Razor page. + /// + public interface IPageActivatorProvider + { + /// + /// Creates a Razor page activator. + /// + /// The . + /// The delegate used to activate the page. + Func CreateActivator(CompiledPageActionDescriptor descriptor); + + /// + /// Releases a Razor page. + /// + /// The . + /// The delegate used to dispose the activated page. + Action CreateReleaser(CompiledPageActionDescriptor descriptor); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageFactoryProvider.cs new file mode 100644 index 0000000000..a3b366784c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageFactoryProvider.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 System; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + /// + /// Provides methods for creation and disposal of Razor pages. + /// + public interface IPageFactoryProvider + { + /// + /// Creates a factory for producing Razor pages for the specified . + /// + /// The . + /// The Razor page factory. + Func CreatePageFactory(CompiledPageActionDescriptor descriptor); + + /// + /// Releases a Razor page. + /// + /// The . + /// The delegate used to release the created page. + Action CreatePageDisposer(CompiledPageActionDescriptor descriptor); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelActivatorProvider.cs new file mode 100644 index 0000000000..8789141087 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelActivatorProvider.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.RazorPages +{ + /// + /// Provides methods to create a Razor Page model. + /// + public interface IPageModelActivatorProvider + { + /// + /// Creates a Razor Page model activator. + /// + /// The . + /// The delegate used to activate the page model. + Func CreateActivator(CompiledPageActionDescriptor descriptor); + + /// + /// Releases a Razor Page model. + /// + /// The . + /// The delegate used to dispose the activated Razor Page model. + Action CreateReleaser(CompiledPageActionDescriptor descriptor); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelFactoryProvider.cs new file mode 100644 index 0000000000..b99a7b1a2c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelFactoryProvider.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.RazorPages +{ + /// + /// Provides methods for creation and disposal of Razor Page models. + /// + public interface IPageModelFactoryProvider + { + /// + /// Creates a factory for producing models for Razor Pages given the specified . + /// + /// The . + /// The Razor Page model factory. + Func CreateModelFactory(CompiledPageActionDescriptor descriptor); + + /// + /// Releases a Razor Page model. + /// + /// The . + /// The delegate used to release the created Razor Page model. + Action CreateModelDisposer(CompiledPageActionDescriptor descriptor); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivatorProvider.cs new file mode 100644 index 0000000000..f0ec8404a9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivatorProvider.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.Expressions; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + /// + /// that uses type activation to create Pages. + /// + public class DefaultPageActivatorProvider : IPageActivatorProvider + { + private readonly Action _disposer = Dispose; + + /// + public virtual Func CreateActivator(CompiledPageActionDescriptor actionDescriptor) + { + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + var pageTypeInfo = actionDescriptor.PageTypeInfo?.AsType(); + if (pageTypeInfo == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(actionDescriptor.PageTypeInfo), + nameof(actionDescriptor)), + nameof(actionDescriptor)); + } + + return CreatePageFactory(pageTypeInfo); + } + + public virtual Action CreateReleaser(CompiledPageActionDescriptor actionDescriptor) + { + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + if (typeof(IDisposable).GetTypeInfo().IsAssignableFrom(actionDescriptor.PageTypeInfo)) + { + return _disposer; + } + + return null; + } + + private static Func CreatePageFactory(Type pageTypeInfo) + { + var parameter1 = Expression.Parameter(typeof(PageContext), "pageContext"); + var parameter2 = Expression.Parameter(typeof(ViewContext), "viewContext"); + + // new Page(); + var newExpression = Expression.New(pageTypeInfo); + + // () => new Page(); + var pageFactory = Expression + .Lambda>(newExpression, parameter1, parameter2) + .Compile(); + return pageFactory; + } + + private static void Dispose(PageContext context, ViewContext viewContext, object page) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (viewContext == null) + { + throw new ArgumentNullException(nameof(viewContext)); + } + + if (page == null) + { + throw new ArgumentNullException(nameof(page)); + } + + ((IDisposable)page).Dispose(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactoryProvider.cs new file mode 100644 index 0000000000..709921ab28 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactoryProvider.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET 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.Reflection; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class DefaultPageFactoryProvider : IPageFactoryProvider + { + private readonly IPageActivatorProvider _pageActivator; + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly RazorPagePropertyActivator.PropertyValueAccessors _propertyAccessors; + + public DefaultPageFactoryProvider( + IPageActivatorProvider pageActivator, + IModelMetadataProvider metadataProvider, + IUrlHelperFactory urlHelperFactory, + IJsonHelper jsonHelper, + DiagnosticSource diagnosticSource, + HtmlEncoder htmlEncoder, + IModelExpressionProvider modelExpressionProvider) + { + _pageActivator = pageActivator; + _modelMetadataProvider = metadataProvider; + _propertyAccessors = new RazorPagePropertyActivator.PropertyValueAccessors + { + UrlHelperAccessor = context => urlHelperFactory.GetUrlHelper(context), + JsonHelperAccessor = context => jsonHelper, + DiagnosticSourceAccessor = context => diagnosticSource, + HtmlEncoderAccessor = context => htmlEncoder, + ModelExpressionProviderAccessor = context => modelExpressionProvider, + }; + } + + public virtual Func CreatePageFactory(CompiledPageActionDescriptor actionDescriptor) + { + if (!typeof(PageBase).GetTypeInfo().IsAssignableFrom(actionDescriptor.PageTypeInfo)) + { + throw new InvalidOperationException(Resources.FormatActivatedInstance_MustBeAnInstanceOf( + _pageActivator.GetType().FullName, + typeof(PageBase).FullName)); + } + + var activatorFactory = _pageActivator.CreateActivator(actionDescriptor); + var declaredModelType = actionDescriptor.DeclaredModelTypeInfo?.AsType() ?? actionDescriptor.PageTypeInfo.AsType(); + var propertyActivator = new RazorPagePropertyActivator( + actionDescriptor.PageTypeInfo.AsType(), + declaredModelType, + _modelMetadataProvider, + _propertyAccessors); + + return (pageContext, viewContext) => + { + var page = (PageBase)activatorFactory(pageContext, viewContext); + page.PageContext = pageContext; + page.Path = pageContext.ActionDescriptor.RelativePath; + page.ViewContext = viewContext; + propertyActivator.Activate(page, viewContext); + return page; + }; + } + + public virtual Action CreatePageDisposer(CompiledPageActionDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + return _pageActivator.CreateReleaser(descriptor); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelActivatorProvider.cs new file mode 100644 index 0000000000..2ec04a81ab --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelActivatorProvider.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.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + /// + /// that uses type activation to create Razor Page instances. + /// + public class DefaultPageModelActivatorProvider : IPageModelActivatorProvider + { + private readonly Action _disposer = Dispose; + + /// + public virtual Func CreateActivator(CompiledPageActionDescriptor actionDescriptor) + { + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + var modelTypeInfo = actionDescriptor.ModelTypeInfo?.AsType(); + if (modelTypeInfo == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(actionDescriptor.ModelTypeInfo), + nameof(actionDescriptor)), + nameof(actionDescriptor)); + } + + var factory = ActivatorUtilities.CreateFactory(modelTypeInfo, Type.EmptyTypes); + return (context) => factory(context.HttpContext.RequestServices, Array.Empty()); + } + + public virtual Action CreateReleaser(CompiledPageActionDescriptor actionDescriptor) + { + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + if (typeof(IDisposable).GetTypeInfo().IsAssignableFrom(actionDescriptor.ModelTypeInfo)) + { + return _disposer; + } + + return null; + } + + private static void Dispose(PageContext context, object page) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (page == null) + { + throw new ArgumentNullException(nameof(page)); + } + + ((IDisposable)page).Dispose(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelFactoryProvider.cs new file mode 100644 index 0000000000..6821916a4f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelFactoryProvider.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.Reflection; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class DefaultPageModelFactoryProvider : IPageModelFactoryProvider + { + private static readonly Func> _createActivateInfo = + CreateActivateInfo; + private readonly IPageModelActivatorProvider _modelActivator; + + public DefaultPageModelFactoryProvider(IPageModelActivatorProvider modelActivator) + { + _modelActivator = modelActivator; + } + + public virtual Func CreateModelFactory(CompiledPageActionDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + if (descriptor.ModelTypeInfo == null) + { + return null; + } + + var modelActivator = _modelActivator.CreateActivator(descriptor); + var propertyActivator = PropertyActivator.GetPropertiesToActivate( + descriptor.ModelTypeInfo.AsType(), + typeof(PageContextAttribute), + _createActivateInfo, + includeNonPublic: false); + + return pageContext => + { + var model = modelActivator(pageContext); + for (var i = 0; i < propertyActivator.Length; i++) + { + propertyActivator[i].Activate(model, pageContext); + } + + return model; + }; + } + + public virtual Action CreateModelDisposer(CompiledPageActionDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + if (descriptor.ModelTypeInfo == null) + { + return null; + } + + return _modelActivator.CreateReleaser(descriptor); + } + + private static PropertyActivator CreateActivateInfo(PropertyInfo property) => + new PropertyActivator(property, pageContext => pageContext); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerMethodDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerMethodDescriptor.cs new file mode 100644 index 0000000000..5372a5d5fa --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerMethodDescriptor.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.Reflection; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class HandlerMethodDescriptor + { + public MethodInfo MethodInfo { get; set; } + + public string HttpMethod { get; set; } + + public string Name { get; set; } + + public IList Parameters { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs new file mode 100644 index 0000000000..cc1be6d7b8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.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 System.Reflection; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Internal; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class HandlerParameterDescriptor : ParameterDescriptor, IParameterInfoParameterDescriptor + { + /// + /// Gets or sets the . + /// + public ParameterInfo ParameterInfo { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageHandlerMethodSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageHandlerMethodSelector.cs new file mode 100644 index 0000000000..06ae8f1f73 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageHandlerMethodSelector.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 Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public interface IPageHandlerMethodSelector + { + HandlerMethodDescriptor Select(PageContext context); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageLoader.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageLoader.cs new file mode 100644 index 0000000000..5d9a9cc0c4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageLoader.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.Infrastructure +{ + /// + /// Creates a from a . + /// + public interface IPageLoader + { + /// + /// Produces a given a . + /// + /// The . + /// The . + CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs new file mode 100644 index 0000000000..7e1dbb47c2 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.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; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class PageActionDescriptorProvider : IActionDescriptorProvider + { + private readonly IPageRouteModelProvider[] _routeModelProviders; + private readonly MvcOptions _mvcOptions; + private readonly IPageRouteModelConvention[] _conventions; + + public PageActionDescriptorProvider( + IEnumerable pageRouteModelProviders, + IOptions mvcOptionsAccessor, + IOptions pagesOptionsAccessor) + { + _routeModelProviders = pageRouteModelProviders.OrderBy(p => p.Order).ToArray(); + _mvcOptions = mvcOptionsAccessor.Value; + + _conventions = pagesOptionsAccessor.Value.Conventions + .OfType() + .ToArray(); + } + + public int Order { get; set; } = -900; // Run after the default MVC provider, but before others. + + public void OnProvidersExecuting(ActionDescriptorProviderContext context) + { + var pageRouteModels = BuildModel(); + + for (var i = 0; i < pageRouteModels.Count; i++) + { + AddActionDescriptors(context.Results, pageRouteModels[i]); + } + } + + protected IList BuildModel() + { + var context = new PageRouteModelProviderContext(); + + for (var i = 0; i < _routeModelProviders.Length; i++) + { + _routeModelProviders[i].OnProvidersExecuting(context); + } + + for (var i = _routeModelProviders.Length - 1; i >= 0; i--) + { + _routeModelProviders[i].OnProvidersExecuted(context); + } + + return context.RouteModels; + } + + public void OnProvidersExecuted(ActionDescriptorProviderContext context) + { + } + + private void AddActionDescriptors(IList actions, PageRouteModel model) + { + for (var i = 0; i < _conventions.Length; i++) + { + _conventions[i].Apply(model); + } + + foreach (var selector in model.Selectors) + { + var descriptor = new PageActionDescriptor + { + AttributeRouteInfo = new AttributeRouteInfo + { + Name = selector.AttributeRouteModel.Name, + Order = selector.AttributeRouteModel.Order ?? 0, + Template = selector.AttributeRouteModel.Template, + SuppressLinkGeneration = selector.AttributeRouteModel.SuppressLinkGeneration, + SuppressPathMatching = selector.AttributeRouteModel.SuppressPathMatching, + }, + DisplayName = $"Page: {model.ViewEnginePath}", + FilterDescriptors = Array.Empty(), + Properties = new Dictionary(model.Properties), + RelativePath = model.RelativePath, + ViewEnginePath = model.ViewEnginePath, + AreaName = model.AreaName, + }; + + foreach (var kvp in model.RouteValues) + { + if (!descriptor.RouteValues.ContainsKey(kvp.Key)) + { + descriptor.RouteValues.Add(kvp.Key, kvp.Value); + } + } + + if (!descriptor.RouteValues.ContainsKey("page")) + { + descriptor.RouteValues.Add("page", model.ViewEnginePath); + } + + actions.Add(descriptor); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageArgumentBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageArgumentBinder.cs new file mode 100644 index 0000000000..a4d8f5cba6 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageArgumentBinder.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 System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + [Obsolete("This type is obsolete and will be removed in a future version.")] + public abstract class PageArgumentBinder + { + public async Task BindModelAsync(PageContext context, Type type, object @default, string name) + { + var result = await BindAsync(context, null, name, type); + return result.IsModelSet ? result.Model : @default; + } + + public Task BindModelAsync(PageContext context, string name) + { + return BindModelAsync(context, default(TModel), name); + } + + public async Task BindModelAsync(PageContext context, TModel @default, string name) + { + var result = await BindAsync(context, null, name, typeof(TModel)); + return result.IsModelSet ? (TModel)result.Model : @default; + } + + public async Task TryUpdateModelAsync(PageContext context, TModel value) + { + var result = await BindAsync(context, value, string.Empty, typeof(TModel)); + return result.IsModelSet && context.ModelState.IsValid; + } + + public async Task TryUpdateModelAsync(PageContext context, TModel value, string name) + { + var result = await BindAsync(context, value, name, typeof(TModel)); + return result.IsModelSet && context.ModelState.IsValid; + } + + protected abstract Task BindAsync(PageContext context, object value, string name, Type type); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs new file mode 100644 index 0000000000..4bb1f43ca9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.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; +using Microsoft.AspNetCore.Mvc.Internal; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class PageBoundPropertyDescriptor : ParameterDescriptor, IPropertyInfoParameterDescriptor + { + /// + /// Gets or sets the for this property. + /// + public PropertyInfo Property { get; set; } + + PropertyInfo IPropertyInfoParameterDescriptor.PropertyInfo => Property; + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageDirectiveFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageDirectiveFeature.cs new file mode 100644 index 0000000000..ff09f6abbf --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageDirectiveFeature.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.IO; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Razor.Extensions; +using Microsoft.AspNetCore.Mvc.RazorPages.Internal; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.AspNetCore.Razor.Language.Intermediate; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public static class PageDirectiveFeature + { + private static readonly RazorProjectEngine PageDirectiveEngine = RazorProjectEngine.Create(RazorConfiguration.Default, new EmptyRazorProjectFileSystem(), builder => + { + for (var i = builder.Phases.Count - 1; i >= 0; i--) + { + var phase = builder.Phases[i]; + builder.Phases.RemoveAt(i); + if (phase is IRazorDocumentClassifierPhase) + { + break; + } + } + + RazorExtensions.Register(builder); + builder.Features.Add(new PageDirectiveParserOptionsFeature()); + }); + + public static bool TryGetPageDirective(ILogger logger, RazorProjectItem projectItem, out string template) + { + if (projectItem == null) + { + throw new ArgumentNullException(nameof(projectItem)); + } + + var codeDocument = PageDirectiveEngine.Process(projectItem); + + var documentIRNode = codeDocument.GetDocumentIntermediateNode(); + if (PageDirective.TryGetPageDirective(documentIRNode, out var pageDirective)) + { + if (pageDirective.DirectiveNode is MalformedDirectiveIntermediateNode malformedNode) + { + logger.MalformedPageDirective(projectItem.FilePath, malformedNode.Diagnostics); + } + + template = pageDirective.RouteTemplate; + return true; + } + + template = null; + return false; + } + + private class PageDirectiveParserOptionsFeature : RazorEngineFeatureBase, IConfigureRazorParserOptionsFeature + { + public int Order { get; } + + public void Configure(RazorParserOptionsBuilder options) + { + options.ParseLeadingDirectives = true; + } + } + + private class EmptyRazorProjectFileSystem : RazorProjectFileSystem + { + public override IEnumerable EnumerateItems(string basePath) + { + return Enumerable.Empty(); + } + + public override IEnumerable FindHierarchicalItems(string basePath, string path, string fileName) + { + return Enumerable.Empty(); + } + + public override RazorProjectItem GetItem(string path) + { + return new NotFoundProjectItem(string.Empty, path); + } + + private class NotFoundProjectItem : RazorProjectItem + { + public NotFoundProjectItem(string basePath, string path) + { + BasePath = basePath; + FilePath = path; + } + + /// + public override string BasePath { get; } + + /// + public override string FilePath { get; } + + /// + public override bool Exists => false; + + /// + public override string PhysicalPath => throw new NotSupportedException(); + + /// + public override Stream Read() => throw new NotSupportedException(); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageModelAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageModelAttribute.cs new file mode 100644 index 0000000000..0ae311a8ee --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageModelAttribute.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 System; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + /// + /// An attribute for base classes for page models. Applying this attribute to a type + /// marks all subclasses of that type as page model types. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public class PageModelAttribute : Attribute + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs new file mode 100644 index 0000000000..10539c0b2a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.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.Diagnostics; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.ViewEngines; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + /// + /// Executes a Razor Page. + /// + public class PageResultExecutor : ViewExecutor + { + private readonly IRazorViewEngine _razorViewEngine; + private readonly IRazorPageActivator _razorPageActivator; + private readonly DiagnosticSource _diagnosticSource; + private readonly HtmlEncoder _htmlEncoder; + + /// + /// Creates a new . + /// + /// The . + /// The . + /// The . + /// The . + /// The . + /// The . + public PageResultExecutor( + IHttpResponseStreamWriterFactory writerFactory, + ICompositeViewEngine compositeViewEngine, + IRazorViewEngine razorViewEngine, + IRazorPageActivator razorPageActivator, + DiagnosticSource diagnosticSource, + HtmlEncoder htmlEncoder) + : base(writerFactory, compositeViewEngine, diagnosticSource) + { + _razorViewEngine = razorViewEngine; + _htmlEncoder = htmlEncoder; + _razorPageActivator = razorPageActivator; + _diagnosticSource = diagnosticSource; + } + + /// + /// Executes a Razor Page asynchronously. + /// + public virtual Task ExecuteAsync(PageContext pageContext, PageResult result) + { + if (pageContext == null) + { + throw new ArgumentNullException(nameof(pageContext)); + } + + if (result == null) + { + throw new ArgumentNullException(nameof(result)); + } + + if (result.Model != null) + { + pageContext.ViewData.Model = result.Model; + } + + OnExecuting(pageContext); + + var viewStarts = new IRazorPage[pageContext.ViewStartFactories.Count]; + for (var i = 0; i < pageContext.ViewStartFactories.Count; i++) + { + viewStarts[i] = pageContext.ViewStartFactories[i](); + } + + var viewContext = result.Page.ViewContext; + viewContext.View = new RazorView( + _razorViewEngine, + _razorPageActivator, + viewStarts, + new RazorPageAdapter(result.Page), + _htmlEncoder, + _diagnosticSource); + + return ExecuteAsync(viewContext, result.ContentType, result.StatusCode); + } + + private void OnExecuting(PageContext pageContext) + { + var viewDataValuesProvider = pageContext.HttpContext.Features.Get(); + if (viewDataValuesProvider != null) + { + viewDataValuesProvider.ProvideViewDataValues(pageContext.ViewData); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageViewLocationExpander.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageViewLocationExpander.cs new file mode 100644 index 0000000000..974bb5f99a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageViewLocationExpander.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.Generic; +using Microsoft.AspNetCore.Mvc.Razor; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class PageViewLocationExpander : IViewLocationExpander + { + public IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations) + { + if ((context.ActionContext.ActionDescriptor is PageActionDescriptor) && !string.IsNullOrEmpty(context.PageName)) + { + return ExpandPageHierarchy(); + } + + // Not a page - just act natural. + return viewLocations; + + IEnumerable ExpandPageHierarchy() + { + foreach (var location in viewLocations) + { + // For pages, we only handle the 'page' token when it's surrounded by slashes. + // + // Explanation: + // We need the ability to 'collapse' the segment which requires us to understand slashes. + // Imagine a path like /{1}/{0} - we might end up with //{0} if we don't do *something* with + // the slashes. Instead of picking on (leading or trailing), we choose both. This seems + // less arbitrary. + // + // + // So given a Page like /Account/Manage/Index using /Pages as the root, and the default set of + // search paths, this will produce the expanded paths: + // + // /Pages/Account/Manage/{0}.cshtml + // /Pages/Account/{0}.cshtml + // /Pages/{0}.cshtml + // /Views/Shared/{0}.cshtml + + if (!location.Contains("/{1}/")) + { + // If the location doesn't have the 'page' replacement token just return it as-is. + yield return location; + continue; + } + + // For locations with the 'page' token - expand them into an ascending directory search, + // but only up to the pages root. + // + // This is easy because the 'page' token already trims the root directory. + var end = context.PageName.Length; + + while (end > 0 && (end = context.PageName.LastIndexOf('/', end - 1)) != -1) + { + // PageName always starts with `/` + yield return location.Replace("/{1}/", context.PageName.Substring(0, end + 1)); + } + } + } + } + + public void PopulateValues(ViewLocationExpanderContext context) + { + // The value we care about - 'page' is already part of the system. We don't need to add it manually. + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAdapter.cs new file mode 100644 index 0000000000..452161083b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAdapter.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET 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.Html; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Rendering; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + // Implements IRazorPage so that RazorPageBase-derived classes don't get activated twice. + // + // The page gets activated before handler methods run, but the RazorView will also activate + // each page. + public class RazorPageAdapter : IRazorPage + { + private readonly RazorPageBase _page; + + public RazorPageAdapter(RazorPageBase page) + { + if (page == null) + { + throw new ArgumentNullException(nameof(page)); + } + + _page = page; + } + + public ViewContext ViewContext + { + get { return _page.ViewContext; } + set { _page.ViewContext = value; } + } + + public IHtmlContent BodyContent + { + get { return _page.BodyContent; } + set { _page.BodyContent = value; } + } + + public bool IsLayoutBeingRendered + { + get { return _page.IsLayoutBeingRendered; } + set { _page.IsLayoutBeingRendered = value; } + } + + public string Path + { + get { return _page.Path; } + set { _page.Path = value; } + } + + public string Layout + { + get { return _page.Layout; } + set { _page.Layout = value; } + } + + public IDictionary PreviousSectionWriters + { + get { return _page.PreviousSectionWriters; } + set { _page.PreviousSectionWriters = value; } + } + + public IDictionary SectionWriters => _page.SectionWriters; + + public void EnsureRenderedBodyOrSections() + { + _page.EnsureRenderedBodyOrSections(); + } + + public Task ExecuteAsync() + { + return _page.ExecuteAsync(); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAttribute.cs new file mode 100644 index 0000000000..900827a018 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAttribute.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; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class RazorPageAttribute : RazorViewAttribute + { + public RazorPageAttribute(string path, Type viewType, string routeTemplate) + : base(path, viewType) + { + RouteTemplate = routeTemplate; + } + + public string RouteTemplate { get; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AuthorizationPageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AuthorizationPageApplicationModelProvider.cs new file mode 100644 index 0000000000..ad047c1ede --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AuthorizationPageApplicationModelProvider.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.Linq; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.AspNetCore.Mvc.Internal; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class AuthorizationPageApplicationModelProvider : IPageApplicationModelProvider + { + private readonly IAuthorizationPolicyProvider _policyProvider; + + public AuthorizationPageApplicationModelProvider(IAuthorizationPolicyProvider policyProvider) + { + _policyProvider = policyProvider; + } + + // The order is set to execute after the DefaultPageApplicationModelProvider. + public int Order => -1000 + 10; + + public void OnProvidersExecuting(PageApplicationModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var pageModel = context.PageApplicationModel; + var authorizeData = pageModel.HandlerTypeAttributes.OfType().ToArray(); + if (authorizeData.Length > 0) + { + pageModel.Filters.Add(AuthorizationApplicationModelProvider.GetFilter(_policyProvider, authorizeData)); + } + foreach (var attribute in pageModel.HandlerTypeAttributes.OfType()) + { + pageModel.Filters.Add(new AllowAnonymousFilter()); + } + } + + public void OnProvidersExecuted(PageApplicationModelProviderContext context) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs new file mode 100644 index 0000000000..2103a4606b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.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; +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/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs new file mode 100644 index 0000000000..29f7214448 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs @@ -0,0 +1,136 @@ +// Copyright (c) .NET 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.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + /// + /// Constructs a from an . + /// + public static class CompiledPageActionDescriptorBuilder + { + /// + /// Creates a from the specified . + /// + /// The . + /// Global filters to apply to the page. + /// The . + public static CompiledPageActionDescriptor Build( + PageApplicationModel applicationModel, + FilterCollection globalFilters) + { + var boundProperties = CreateBoundProperties(applicationModel); + var filters = Enumerable.Concat( + globalFilters.Select(f => new FilterDescriptor(f, FilterScope.Global)), + applicationModel.Filters.Select(f => new FilterDescriptor(f, FilterScope.Action))) + .ToArray(); + var handlerMethods = CreateHandlerMethods(applicationModel); + + if (applicationModel.ModelType != null && applicationModel.DeclaredModelType != null && + !applicationModel.DeclaredModelType.IsAssignableFrom(applicationModel.ModelType)) + { + var message = Resources.FormatInvalidActionDescriptorModelType( + applicationModel.ActionDescriptor.DisplayName, + applicationModel.ModelType.Name, + applicationModel.DeclaredModelType.Name); + + throw new InvalidOperationException(message); + } + + var actionDescriptor = applicationModel.ActionDescriptor; + return new CompiledPageActionDescriptor(actionDescriptor) + { + ActionConstraints = actionDescriptor.ActionConstraints, + AttributeRouteInfo = actionDescriptor.AttributeRouteInfo, + BoundProperties = boundProperties, + FilterDescriptors = filters, + HandlerMethods = handlerMethods, + HandlerTypeInfo = applicationModel.HandlerType, + DeclaredModelTypeInfo = applicationModel.DeclaredModelType, + ModelTypeInfo = applicationModel.ModelType, + RouteValues = actionDescriptor.RouteValues, + PageTypeInfo = applicationModel.PageType, + Properties = applicationModel.Properties, + }; + } + + // Internal for unit testing + internal static HandlerMethodDescriptor[] CreateHandlerMethods(PageApplicationModel applicationModel) + { + var handlerModels = applicationModel.HandlerMethods; + var handlerDescriptors = new HandlerMethodDescriptor[handlerModels.Count]; + + for (var i = 0; i < handlerDescriptors.Length; i++) + { + var handlerModel = handlerModels[i]; + + handlerDescriptors[i] = new HandlerMethodDescriptor + { + HttpMethod = handlerModel.HttpMethod, + Name = handlerModel.HandlerName, + MethodInfo = handlerModel.MethodInfo, + Parameters = CreateHandlerParameters(handlerModel), + }; + } + + return handlerDescriptors; + } + + // internal for unit testing + internal static HandlerParameterDescriptor[] CreateHandlerParameters(PageHandlerModel handlerModel) + { + var methodParameters = handlerModel.Parameters; + var parameters = new HandlerParameterDescriptor[methodParameters.Count]; + + for (var i = 0; i < parameters.Length; i++) + { + var parameterModel = methodParameters[i]; + + parameters[i] = new HandlerParameterDescriptor + { + BindingInfo = parameterModel.BindingInfo, + Name = parameterModel.ParameterName, + ParameterInfo = parameterModel.ParameterInfo, + ParameterType = parameterModel.ParameterInfo.ParameterType, + }; + } + + return parameters; + } + + // internal for unit testing + internal static PageBoundPropertyDescriptor[] CreateBoundProperties(PageApplicationModel applicationModel) + { + var results = new List(); + var properties = applicationModel.HandlerProperties; + for (var i = 0; i < properties.Count; i++) + { + var propertyModel = properties[i]; + + // Only add properties which are explicitly marked to bind. + if (propertyModel.BindingInfo == null) + { + continue; + } + + var descriptor = new PageBoundPropertyDescriptor + { + Property = propertyModel.PropertyInfo, + Name = propertyModel.PropertyName, + BindingInfo = propertyModel.BindingInfo, + ParameterType = propertyModel.PropertyInfo.PropertyType, + }; + + results.Add(descriptor); + } + + return results.ToArray(); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs new file mode 100644 index 0000000000..544521e5a7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.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; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Mvc.Razor.Extensions; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Razor.Hosting; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class CompiledPageRouteModelProvider : IPageRouteModelProvider + { + private readonly ApplicationPartManager _applicationManager; + private readonly RazorPagesOptions _pagesOptions; + private readonly RazorProjectEngine _razorProjectEngine; + private readonly ILogger _logger; + private readonly PageRouteModelFactory _routeModelFactory; + + public CompiledPageRouteModelProvider( + ApplicationPartManager applicationManager, + IOptions pagesOptionsAccessor, + RazorProjectEngine razorProjectEngine, + ILogger logger) + { + _applicationManager = applicationManager ?? throw new ArgumentNullException(nameof(applicationManager)); + _pagesOptions = pagesOptionsAccessor?.Value ?? throw new ArgumentNullException(nameof(pagesOptionsAccessor)); + _razorProjectEngine = razorProjectEngine ?? throw new ArgumentNullException(nameof(razorProjectEngine)); + _logger = logger ?? throw new ArgumentNullException(nameof(razorProjectEngine)); + _routeModelFactory = new PageRouteModelFactory(_pagesOptions, _logger); + } + + public int Order => -1000; + + public void OnProvidersExecuting(PageRouteModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + CreateModels(context); + } + + public void OnProvidersExecuted(PageRouteModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + } + + private IEnumerable GetViewDescriptors(ApplicationPartManager applicationManager) + { + if (applicationManager == null) + { + throw new ArgumentNullException(nameof(applicationManager)); + } + + var viewsFeature = GetViewFeature(applicationManager); + + var visited = new HashSet(StringComparer.OrdinalIgnoreCase); + foreach (var viewDescriptor in viewsFeature.ViewDescriptors) + { + if (!visited.Add(viewDescriptor.RelativePath)) + { + // Already seen an descriptor with a higher "order" + continue; + } + + if (!viewDescriptor.IsPrecompiled) + { + continue; + } + + if (IsRazorPage(viewDescriptor)) + { + yield return viewDescriptor; + } + } + + bool IsRazorPage(CompiledViewDescriptor viewDescriptor) + { + if (viewDescriptor.Item != null) + { + return viewDescriptor.Item.Kind == RazorPageDocumentClassifierPass.RazorPageDocumentKind; + } + else if (viewDescriptor.ViewAttribute != null) + { + return viewDescriptor.ViewAttribute is RazorPageAttribute; + } + + return false; + } + } + + protected virtual ViewsFeature GetViewFeature(ApplicationPartManager applicationManager) + { + var viewsFeature = new ViewsFeature(); + applicationManager.PopulateFeature(viewsFeature); + return viewsFeature; + } + + private void CreateModels(PageRouteModelProviderContext context) + { + var rootDirectory = _pagesOptions.RootDirectory; + if (!rootDirectory.EndsWith("/", StringComparison.Ordinal)) + { + rootDirectory = rootDirectory + "/"; + } + + var areaRootDirectory = "/Areas/"; + foreach (var viewDescriptor in GetViewDescriptors(_applicationManager)) + { + if (viewDescriptor.Item != null && !ChecksumValidator.IsItemValid(_razorProjectEngine.FileSystem, viewDescriptor.Item)) + { + // If we get here, this compiled Page has different local content, so ignore it. + continue; + } + + var relativePath = viewDescriptor.RelativePath; + var routeTemplate = GetRouteTemplate(viewDescriptor); + PageRouteModel routeModel = null; + + // When RootDirectory and AreaRootDirectory overlap (e.g. RootDirectory = '/', AreaRootDirectory = '/Areas'), we + // only want to allow a page to be associated with the area route. + if (_pagesOptions.AllowAreas && relativePath.StartsWith(areaRootDirectory, StringComparison.OrdinalIgnoreCase)) + { + routeModel = _routeModelFactory.CreateAreaRouteModel(relativePath, routeTemplate); + } + else if (relativePath.StartsWith(rootDirectory, StringComparison.OrdinalIgnoreCase)) + { + routeModel = _routeModelFactory.CreateRouteModel(relativePath, routeTemplate); + } + + if (routeModel != null) + { + context.RouteModels.Add(routeModel); + } + } + } + + internal static string GetRouteTemplate(CompiledViewDescriptor viewDescriptor) + { + if (viewDescriptor.ViewAttribute != null) + { + return ((RazorPageAttribute)viewDescriptor.ViewAttribute).RouteTemplate; + } + + if (viewDescriptor.Item != null) + { + return viewDescriptor.Item.Metadata + .OfType() + .FirstOrDefault(f => f.Key == RazorPageDocumentClassifierPass.RouteTemplateKey) + ?.Value; + } + + return null; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs new file mode 100644 index 0000000000..cc6a02af02 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.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.Reflection; +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.Infrastructure; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public 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 Func _supportsAllRequests; + private readonly Func _supportsNonGetRequests; + + + public DefaultPageApplicationModelProvider( + IModelMetadataProvider modelMetadataProvider, + IOptions options) + { + _modelMetadataProvider = modelMetadataProvider; + _options = options.Value; + + _supportsAllRequests = _ => true; + _supportsNonGetRequests = context => !string.Equals(context.HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase); + } + + /// + public int Order => -1000; + + /// + public void OnProvidersExecuting(PageApplicationModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + context.PageApplicationModel = CreateModel(context.ActionDescriptor, context.PageType); + } + + /// + public void OnProvidersExecuted(PageApplicationModelProviderContext context) + { + } + + /// + /// Creates a for the given . + /// + /// The . + /// The . + /// A for the given . + protected virtual PageApplicationModel CreateModel( + PageActionDescriptor actionDescriptor, + TypeInfo pageTypeInfo) + { + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + if (pageTypeInfo == null) + { + throw new ArgumentNullException(nameof(pageTypeInfo)); + } + + if (!typeof(PageBase).GetTypeInfo().IsAssignableFrom(pageTypeInfo)) + { + throw new InvalidOperationException(Resources.FormatInvalidPageType_WrongBase( + pageTypeInfo.FullName, + typeof(PageBase).FullName)); + } + + // Pages always have a model type. If it's not set explicitly by the developer using + // @model, it will be the same as the page type. + var modelProperty = pageTypeInfo.GetProperty(ModelPropertyName, BindingFlags.Public | BindingFlags.Instance); + if (modelProperty == null) + { + throw new InvalidOperationException(Resources.FormatInvalidPageType_NoModelProperty( + pageTypeInfo.FullName, + ModelPropertyName)); + } + + var modelTypeInfo = modelProperty.PropertyType.GetTypeInfo(); + var declaredModelType = modelTypeInfo; + + // Now we want figure out which type is the handler type. + TypeInfo handlerType; + if (modelProperty.PropertyType.IsDefined(typeof(PageModelAttribute), inherit: true)) + { + handlerType = modelTypeInfo; + } + else + { + handlerType = pageTypeInfo; + } + + var handlerTypeAttributes = handlerType.GetCustomAttributes(inherit: true); + var pageModel = new PageApplicationModel( + actionDescriptor, + declaredModelType, + handlerType, + handlerTypeAttributes) + { + PageType = pageTypeInfo, + ModelType = modelTypeInfo, + }; + + PopulateHandlerMethods(pageModel); + PopulateHandlerProperties(pageModel); + PopulateFilters(pageModel); + + return pageModel; + } + + // Internal for unit testing + internal void PopulateHandlerProperties(PageApplicationModel pageModel) + { + var properties = PropertyHelper.GetVisibleProperties(pageModel.HandlerType.AsType()); + + for (var i = 0; i < properties.Length; i++) + { + var propertyModel = CreatePropertyModel(properties[i].Property); + if (propertyModel != null) + { + propertyModel.Page = pageModel; + pageModel.HandlerProperties.Add(propertyModel); + } + } + } + + // Internal for unit testing + internal void PopulateHandlerMethods(PageApplicationModel pageModel) + { + var methods = pageModel.HandlerType.GetMethods(); + + for (var i = 0; i < methods.Length; i++) + { + var handler = CreateHandlerModel(methods[i]); + if (handler != null) + { + pageModel.HandlerMethods.Add(handler); + } + } + } + + internal void PopulateFilters(PageApplicationModel pageModel) + { + for (var i = 0; i < pageModel.HandlerTypeAttributes.Count; i++) + { + if (pageModel.HandlerTypeAttributes[i] is IFilterMetadata filter) + { + pageModel.Filters.Add(filter); + } + } + + if (typeof(IAsyncPageFilter).IsAssignableFrom(pageModel.HandlerType) || + typeof(IPageFilter).IsAssignableFrom(pageModel.HandlerType)) + { + pageModel.Filters.Add(_pageHandlerPageFilter); + } + + if (typeof(IAsyncResultFilter).IsAssignableFrom(pageModel.HandlerType) || + typeof(IResultFilter).IsAssignableFrom(pageModel.HandlerType)) + { + pageModel.Filters.Add(_pageHandlerResultFilter); + } + } + + /// + /// Creates a for the specified .s + /// + /// The . + /// The . + protected virtual PageHandlerModel CreateHandlerModel(MethodInfo method) + { + if (method == null) + { + throw new ArgumentNullException(nameof(method)); + } + + if (!IsHandler(method)) + { + return null; + } + + if (!TryParseHandlerMethod(method.Name, out var httpMethod, out var handlerName)) + { + return null; + } + + var handlerModel = new PageHandlerModel( + method, + method.GetCustomAttributes(inherit: true)) + { + Name = method.Name, + HandlerName = handlerName, + HttpMethod = httpMethod, + }; + + var methodParameters = handlerModel.MethodInfo.GetParameters(); + + for (var i = 0; i < methodParameters.Length; i++) + { + var parameter = methodParameters[i]; + var parameterModel = CreateParameterModel(parameter); + parameterModel.Handler = handlerModel; + + handlerModel.Parameters.Add(parameterModel); + } + + return handlerModel; + } + + /// + /// Creates a for the specified . + /// + /// The . + /// The . + protected virtual PageParameterModel CreateParameterModel(ParameterInfo parameter) + { + if (parameter == null) + { + throw new ArgumentNullException(nameof(parameter)); + } + + var attributes = parameter.GetCustomAttributes(inherit: true); + + BindingInfo bindingInfo; + if (_options.AllowValidatingTopLevelNodes && _modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase) + { + var modelMetadata = modelMetadataProviderBase.GetMetadataForParameter(parameter); + bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); + } + else + { + bindingInfo = BindingInfo.GetBindingInfo(attributes); + } + + return new PageParameterModel(parameter, attributes) + { + BindingInfo = bindingInfo, + ParameterName = parameter.Name, + }; + } + + /// + /// Creates a for the . + /// + /// The . + /// The . + protected virtual PagePropertyModel CreatePropertyModel(PropertyInfo property) + { + if (property == null) + { + throw new ArgumentNullException(nameof(property)); + } + + var propertyAttributes = property.GetCustomAttributes(inherit: true); + + // BindingInfo for properties can be either specified by decorating the property with binding-specific attributes. + // ModelMetadata also adds information from the property's type and any configured IBindingMetadataProvider. + var propertyMetadata = _modelMetadataProvider.GetMetadataForProperty(property.DeclaringType, property.Name); + var bindingInfo = BindingInfo.GetBindingInfo(propertyAttributes, propertyMetadata); + + if (bindingInfo == null) + { + // Look for BindPropertiesAttribute on the handler type if no BindingInfo was inferred for the property. + // This allows a user to enable model binding on properties by decorating the controller type with BindPropertiesAttribute. + var declaringType = property.DeclaringType; + var bindPropertiesAttribute = declaringType.GetCustomAttribute(inherit: true); + if (bindPropertiesAttribute != null) + { + var requestPredicate = bindPropertiesAttribute.SupportsGet ? _supportsAllRequests : _supportsNonGetRequests; + bindingInfo = new BindingInfo + { + RequestPredicate = requestPredicate, + }; + } + } + + var model = new PagePropertyModel(property, propertyAttributes) + { + PropertyName = property.Name, + BindingInfo = bindingInfo, + }; + + return model; + } + + /// + /// Determines if the specified is a handler. + /// + /// The . + /// true if the is a handler. Otherwise false. + /// + /// Override this method to provide custom logic to determine which methods are considered handlers. + /// + protected virtual bool IsHandler(MethodInfo methodInfo) + { + // The SpecialName bit is set to flag members that are treated in a special way by some compilers + // (such as property accessors and operator overloading methods). + if (methodInfo.IsSpecialName) + { + return false; + } + + // Overridden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid. + if (methodInfo.GetBaseDefinition().DeclaringType == typeof(object)) + { + return false; + } + + if (methodInfo.IsStatic) + { + return false; + } + + if (methodInfo.IsAbstract) + { + return false; + } + + if (methodInfo.IsConstructor) + { + return false; + } + + if (methodInfo.IsGenericMethod) + { + return false; + } + + if (!methodInfo.IsPublic) + { + return false; + } + + if (methodInfo.IsDefined(typeof(NonHandlerAttribute))) + { + return false; + } + + // Exclude the whole hierarchy of Page. + var declaringType = methodInfo.DeclaringType; + if (declaringType == typeof(Page) || + declaringType == typeof(PageBase) || + declaringType == typeof(RazorPageBase)) + { + return false; + } + + // Exclude methods declared on PageModel + if (declaringType == typeof(PageModel)) + { + return false; + } + + return true; + } + + internal static bool TryParseHandlerMethod(string methodName, out string httpMethod, out string handler) + { + httpMethod = null; + handler = null; + + // Handler method names always start with "On" + if (!methodName.StartsWith("On") || methodName.Length <= "On".Length) + { + return false; + } + + // Now we parse the method name according to our conventions to determine the required HTTP method + // and optional 'handler name'. + // + // Valid names look like: + // - OnGet + // - OnPost + // - OnFooBar + // - OnTraceAsync + // - OnPostEditAsync + + var start = "On".Length; + var length = methodName.Length; + if (methodName.EndsWith("Async", StringComparison.Ordinal)) + { + length -= "Async".Length; + } + + if (start == length) + { + // There are no additional characters. This is "On" or "OnAsync". + return false; + } + + // The http method follows "On" and is required to be at least one character. We use casing + // to determine where it ends. + var handlerNameStart = start + 1; + for (; handlerNameStart < length; handlerNameStart++) + { + if (char.IsUpper(methodName[handlerNameStart])) + { + break; + } + } + + httpMethod = methodName.Substring(start, handlerNameStart - start); + + // The handler name follows the http method and is optional. It includes everything up to the end + // excluding the "Async" suffix (if present). + handler = handlerNameStart == length ? null : methodName.Substring(handlerNameStart, length - handlerNameStart); + return true; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs new file mode 100644 index 0000000000..7b3310bbdd --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.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 System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ +#pragma warning disable CS0618 // Type or member is obsolete + public class DefaultPageArgumentBinder : PageArgumentBinder +#pragma warning restore CS0618 // Type or member is obsolete + { + private readonly ParameterBinder _parameterBinder; + + public DefaultPageArgumentBinder(ParameterBinder binder) + { + _parameterBinder = binder; + } + + protected override async Task BindAsync(PageContext pageContext, object value, string name, Type type) + { + var valueProvider = await GetCompositeValueProvider(pageContext); + var parameterDescriptor = new ParameterDescriptor + { + BindingInfo = null, + Name = name, + ParameterType = type, + }; + + return await _parameterBinder.BindModelAsync(pageContext, valueProvider, parameterDescriptor, value); + } + + private static async Task GetCompositeValueProvider(PageContext pageContext) + { + var factories = pageContext.ValueProviderFactories; + var valueProviderFactoryContext = new ValueProviderFactoryContext(pageContext); + for (var i = 0; i < factories.Count; i++) + { + var factory = factories[i]; + await factory.CreateValueProviderAsync(valueProviderFactoryContext); + } + + return new CompositeValueProvider(valueProviderFactoryContext.ValueProviders); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs new file mode 100644 index 0000000000..2369705ed7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs @@ -0,0 +1,195 @@ +// Copyright (c) .NET 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.Extensions.Options; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class DefaultPageHandlerMethodSelector : IPageHandlerMethodSelector + { + private const string Handler = "handler"; + + private readonly IOptions _options; + + [Obsolete("This constructor will be removed in a future release. Use the other constructor.")] + public DefaultPageHandlerMethodSelector() + { + } + + public DefaultPageHandlerMethodSelector(IOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + _options = options; + } + + private bool AllowFuzzyHttpMethodMatching => _options?.Value.AllowMappingHeadRequestsToGetHandler ?? false; + + public HandlerMethodDescriptor Select(PageContext context) + { + var handlers = SelectHandlers(context); + if (handlers == null || handlers.Count == 0) + { + return null; + } + + List ambiguousMatches = null; + HandlerMethodDescriptor bestMatch = null; + for (var score = 2; score >= 0; score--) + { + for (var i = 0; i < handlers.Count; i++) + { + var handler = handlers[i]; + if (GetScore(handler) == score) + { + if (bestMatch == null) + { + bestMatch = handler; + continue; + } + + if (ambiguousMatches == null) + { + ambiguousMatches = new List(); + ambiguousMatches.Add(bestMatch); + } + + ambiguousMatches.Add(handler); + } + } + + if (ambiguousMatches != null) + { + var ambiguousMethods = string.Join(", ", ambiguousMatches.Select(m => m.MethodInfo)); + throw new InvalidOperationException(Resources.FormatAmbiguousHandler(Environment.NewLine, ambiguousMethods)); + } + + if (bestMatch != null) + { + return bestMatch; + } + } + + return null; + } + + private List SelectHandlers(PageContext context) + { + var handlers = context.ActionDescriptor.HandlerMethods; + var candidates = new List(); + + // Name is optional, may not be provided. + var handlerName = GetHandlerName(context); + + // The handler selection process considers handlers according to a few criteria. Handlers + // have a defined HTTP method that they handle, and also optionally a 'name'. + // + // We don't really have a scenario for handler methods without a verb (we don't provide a way + // to create one). If we see one, it will just never match. + // + // The verb must match (with some fuzzy matching) and the handler name must match if + // there is one. + // + // The process is like this: + // + // 1. Match the possible candidates on HTTP method + // 1a. **Added in 2.1** if no candidates matched in 1, then do *fuzzy matching* + // 2. Match the candidates from 1 or 1a on handler name. + + // Step 1: match on HTTP method. + var httpMethod = context.HttpContext.Request.Method; + for (var i = 0; i < handlers.Count; i++) + { + var handler = handlers[i]; + if (handler.HttpMethod != null && + string.Equals(handler.HttpMethod, httpMethod, StringComparison.OrdinalIgnoreCase)) + { + candidates.Add(handler); + } + } + + // Step 1a: do fuzzy HTTP method matching if needed. + if (candidates.Count == 0 && AllowFuzzyHttpMethodMatching) + { + var fuzzyHttpMethod = GetFuzzyMatchHttpMethod(context); + if (fuzzyHttpMethod != null) + { + for (var i = 0; i < handlers.Count; i++) + { + var handler = handlers[i]; + if (handler.HttpMethod != null && + string.Equals(handler.HttpMethod, fuzzyHttpMethod, StringComparison.OrdinalIgnoreCase)) + { + candidates.Add(handler); + } + } + } + } + + // Step 2: remove candidates with non-matching handlers. + for (var i = candidates.Count - 1; i >= 0; i--) + { + var handler = candidates[i]; + if (handler.Name != null && + !handler.Name.Equals(handlerName, StringComparison.OrdinalIgnoreCase)) + { + candidates.RemoveAt(i); + } + } + + return candidates; + } + + private static int GetScore(HandlerMethodDescriptor descriptor) + { + if (descriptor.Name != null) + { + return 2; + } + else if (descriptor.HttpMethod != null) + { + return 1; + } + else + { + return 0; + } + } + + private static string GetHandlerName(PageContext context) + { + var handlerName = Convert.ToString(context.RouteData.Values[Handler]); + if (!string.IsNullOrEmpty(handlerName)) + { + return handlerName; + } + + if (context.HttpContext.Request.Query.TryGetValue(Handler, out StringValues queryValues)) + { + return queryValues[0]; + } + + return null; + } + + private static string GetFuzzyMatchHttpMethod(PageContext context) + { + var httpMethod = context.HttpContext.Request.Method; + + // Map HEAD to get. + if (string.Equals("HEAD", httpMethod, StringComparison.OrdinalIgnoreCase)) + { + return "GET"; + } + + return null; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs new file mode 100644 index 0000000000..9bb9390bcc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs @@ -0,0 +1,114 @@ +// Copyright (c) .NET 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.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class DefaultPageLoader : IPageLoader + { + private readonly IPageApplicationModelProvider[] _applicationModelProviders; + private readonly IViewCompilerProvider _viewCompilerProvider; + private readonly PageConventionCollection _conventions; + private readonly FilterCollection _globalFilters; + + public DefaultPageLoader( + IEnumerable applicationModelProviders, + IViewCompilerProvider viewCompilerProvider, + IOptions pageOptions, + IOptions mvcOptions) + { + _applicationModelProviders = applicationModelProviders + .OrderBy(p => p.Order) + .ToArray(); + _viewCompilerProvider = viewCompilerProvider; + _conventions = pageOptions.Value.Conventions; + _globalFilters = mvcOptions.Value.Filters; + } + + private IViewCompiler Compiler => _viewCompilerProvider.GetCompiler(); + + public CompiledPageActionDescriptor Load(PageActionDescriptor actionDescriptor) + { + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + var compileTask = Compiler.CompileAsync(actionDescriptor.RelativePath); + var viewDescriptor = compileTask.GetAwaiter().GetResult(); + + var context = new PageApplicationModelProviderContext(actionDescriptor, viewDescriptor.Type.GetTypeInfo()); + for (var i = 0; i < _applicationModelProviders.Length; i++) + { + _applicationModelProviders[i].OnProvidersExecuting(context); + } + + for (var i = _applicationModelProviders.Length - 1; i >= 0; i--) + { + _applicationModelProviders[i].OnProvidersExecuted(context); + } + + ApplyConventions(_conventions, context.PageApplicationModel); + + return CompiledPageActionDescriptorBuilder.Build(context.PageApplicationModel, _globalFilters); + } + + internal static void ApplyConventions( + PageConventionCollection conventions, + PageApplicationModel pageApplicationModel) + { + var applicationModelConventions = GetConventions(pageApplicationModel.HandlerTypeAttributes); + foreach (var convention in applicationModelConventions) + { + convention.Apply(pageApplicationModel); + } + + var handlers = pageApplicationModel.HandlerMethods.ToArray(); + foreach (var handlerModel in handlers) + { + var handlerModelConventions = GetConventions(handlerModel.Attributes); + foreach (var convention in handlerModelConventions) + { + convention.Apply(handlerModel); + } + + var parameterModels = handlerModel.Parameters.ToArray(); + foreach (var parameterModel in parameterModels) + { + var parameterModelConventions = GetConventions(parameterModel.Attributes); + foreach (var convention in parameterModelConventions) + { + convention.Apply(parameterModel); + } + } + } + + var properties = pageApplicationModel.HandlerProperties.ToArray(); + foreach (var propertyModel in properties) + { + var propertyModelConventions = GetConventions(propertyModel.Attributes); + foreach (var convention in propertyModelConventions) + { + convention.Apply(propertyModel); + } + } + + IEnumerable GetConventions( + IReadOnlyList attributes) + { + return Enumerable.Concat( + conventions.OfType(), + attributes.OfType()); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs new file mode 100644 index 0000000000..6bea1e9605 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs @@ -0,0 +1,202 @@ +// Copyright (c) .NET 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.Linq.Expressions; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public static class ExecutorFactory + { + public static PageHandlerExecutorDelegate CreateExecutor(HandlerMethodDescriptor handlerDescriptor) + { + if (handlerDescriptor == null) + { + throw new ArgumentNullException(nameof(handlerDescriptor)); + } + + var handler = CreateHandlerMethod(handlerDescriptor); + + return handler.Execute; + } + + private static HandlerMethod CreateHandlerMethod(HandlerMethodDescriptor handlerDescriptor) + { + var method = handlerDescriptor.MethodInfo; + var parameters = handlerDescriptor.Parameters.ToArray(); + + var returnType = method.ReturnType; + if (returnType == typeof(void)) + { + return new VoidHandlerMethod(parameters, method); + } + else if (typeof(IActionResult).IsAssignableFrom(returnType)) + { + return new ActionResultHandlerMethod(parameters, method); + } + else if (returnType == typeof(Task)) + { + return new NonGenericTaskHandlerMethod(parameters, method); + } + else + { + var taskType = ClosedGenericMatcher.ExtractGenericInterface(returnType, typeof(Task<>)); + if (taskType != null && typeof(IActionResult).IsAssignableFrom(taskType.GenericTypeArguments[0])) + { + return new GenericTaskHandlerMethod(parameters, method); + } + } + + throw new InvalidOperationException(Resources.FormatUnsupportedHandlerMethodType(returnType)); + } + + private abstract class HandlerMethod + { + protected static Expression[] Unpack(Expression arguments, HandlerParameterDescriptor[] parameters) + { + var unpackExpressions = new Expression[parameters.Length]; + for (var i = 0; i < parameters.Length; i++) + { + unpackExpressions[i] = Expression.Convert( + Expression.ArrayIndex(arguments, Expression.Constant(i)), + parameters[i].ParameterType); + } + + return unpackExpressions; + } + + protected HandlerMethod(HandlerParameterDescriptor[] parameters) + { + Parameters = parameters; + } + + public HandlerParameterDescriptor[] Parameters { get; } + + public abstract Task Execute(object receiver, object[] arguments); + } + + private class NonGenericTaskHandlerMethod : HandlerMethod + { + private readonly Func _thunk; + + public NonGenericTaskHandlerMethod(HandlerParameterDescriptor[] parameters, MethodInfo method) + : base(parameters) + { + var receiver = Expression.Parameter(typeof(object), "receiver"); + var arguments = Expression.Parameter(typeof(object[]), "arguments"); + + _thunk = Expression.Lambda>( + Expression.Call( + Expression.Convert(receiver, method.DeclaringType), + method, + Unpack(arguments, parameters)), + receiver, + arguments).Compile(); + } + + public override async Task Execute(object receiver, object[] arguments) + { + await _thunk(receiver, arguments); + return null; + } + } + + private class GenericTaskHandlerMethod : HandlerMethod + { + private static readonly MethodInfo ConvertMethod = typeof(GenericTaskHandlerMethod).GetMethod( + nameof(Convert), + BindingFlags.NonPublic | BindingFlags.Static); + + private readonly Func> _thunk; + + public GenericTaskHandlerMethod(HandlerParameterDescriptor[] parameters, MethodInfo method) + : base(parameters) + { + var receiver = Expression.Parameter(typeof(object), "receiver"); + var arguments = Expression.Parameter(typeof(object[]), "arguments"); + + _thunk = Expression.Lambda>>( + Expression.Call( + ConvertMethod.MakeGenericMethod(method.ReturnType.GenericTypeArguments), + Expression.Convert( + Expression.Call( + Expression.Convert(receiver, method.DeclaringType), + method, + Unpack(arguments, parameters)), + typeof(object))), + receiver, + arguments).Compile(); + } + + public override async Task Execute(object receiver, object[] arguments) + { + var result = await _thunk(receiver, arguments); + return (IActionResult)result; + } + + private static async Task Convert(object taskAsObject) + { + var task = (Task)taskAsObject; + return await task; + } + } + + private class VoidHandlerMethod : HandlerMethod + { + private readonly Action _thunk; + + public VoidHandlerMethod(HandlerParameterDescriptor[] parameters, MethodInfo method) + : base(parameters) + { + var receiver = Expression.Parameter(typeof(object), "receiver"); + var arguments = Expression.Parameter(typeof(object[]), "arguments"); + + _thunk = Expression.Lambda>( + Expression.Call( + Expression.Convert(receiver, method.DeclaringType), + method, + Unpack(arguments, parameters)), + receiver, + arguments).Compile(); + } + + public override Task Execute(object receiver, object[] arguments) + { + _thunk(receiver, arguments); + return Task.FromResult(null); + } + } + + private class ActionResultHandlerMethod : HandlerMethod + { + private readonly Func _thunk; + + public ActionResultHandlerMethod(HandlerParameterDescriptor[] parameters, MethodInfo method) + : base(parameters) + { + var receiver = Expression.Parameter(typeof(object), "receiver"); + var arguments = Expression.Parameter(typeof(object[]), "arguments"); + + _thunk = Expression.Lambda>( + Expression.Convert( + Expression.Call( + Expression.Convert(receiver, method.DeclaringType), + method, + Unpack(arguments, parameters)), + typeof(IActionResult)), + receiver, + arguments).Compile(); + } + + public override Task Execute(object receiver, object[] arguments) + { + return Task.FromResult(_thunk(receiver, arguments)); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/MvcRazorPagesDiagnosticSourceExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/MvcRazorPagesDiagnosticSourceExtensions.cs new file mode 100644 index 0000000000..fd7ca7f0bc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/MvcRazorPagesDiagnosticSourceExtensions.cs @@ -0,0 +1,289 @@ +// Copyright (c) .NET 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.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public static class MvcRazorPagesDiagnosticSourceExtensions + { + public static void BeforeHandlerMethod( + this DiagnosticSource diagnosticSource, + ActionContext actionContext, + HandlerMethodDescriptor handlerMethodDescriptor, + IDictionary arguments, + object instance) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionContext != null); + Debug.Assert(handlerMethodDescriptor != null); + Debug.Assert(arguments != null); + Debug.Assert(instance != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeHandlerMethod")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeHandlerMethod", + new + { + actionContext = actionContext, + arguments = arguments, + handlerMethodDescriptor = handlerMethodDescriptor, + instance = instance, + }); + } + } + + public static void AfterHandlerMethod( + this DiagnosticSource diagnosticSource, + ActionContext actionContext, + HandlerMethodDescriptor handlerMethodDescriptor, + IDictionary arguments, + object instance, + IActionResult result) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(actionContext != null); + Debug.Assert(handlerMethodDescriptor != null); + Debug.Assert(arguments != null); + Debug.Assert(instance != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterHandlerMethod")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterHandlerMethod", + new + { + actionContext = actionContext, + arguments = arguments, + handlerMethodDescriptor = handlerMethodDescriptor, + instance = instance, + result = result + }); + } + } + + public static void BeforeOnPageHandlerExecution( + this DiagnosticSource diagnosticSource, + PageHandlerExecutingContext handlerExecutionContext, + IAsyncPageFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(handlerExecutionContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecution")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecution", + new + { + actionDescriptor = handlerExecutionContext.ActionDescriptor, + handlerExecutionContext = handlerExecutionContext, + filter = filter + }); + } + } + + public static void AfterOnPageHandlerExecution( + this DiagnosticSource diagnosticSource, + PageHandlerExecutedContext handlerExecutedContext, + IAsyncPageFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(handlerExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecution")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecution", + new + { + actionDescriptor = handlerExecutedContext.ActionDescriptor, + handlerExecutedContext = handlerExecutedContext, + filter = filter + }); + } + } + + public static void BeforeOnPageHandlerExecuting( + this DiagnosticSource diagnosticSource, + PageHandlerExecutingContext handlerExecutingContext, + IPageFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(handlerExecutingContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecuting")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecuting", + new + { + actionDescriptor = handlerExecutingContext.ActionDescriptor, + handlerExecutingContext = handlerExecutingContext, + filter = filter + }); + } + } + + public static void AfterOnPageHandlerExecuting( + this DiagnosticSource diagnosticSource, + PageHandlerExecutingContext handlerExecutingContext, + IPageFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(handlerExecutingContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecuting")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecuting", + new + { + actionDescriptor = handlerExecutingContext.ActionDescriptor, + handlerExecutingContext = handlerExecutingContext, + filter = filter + }); + } + } + + public static void BeforeOnPageHandlerExecuted( + this DiagnosticSource diagnosticSource, + PageHandlerExecutedContext handlerExecutedContext, + IPageFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(handlerExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecuted")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerExecuted", + new + { + actionDescriptor = handlerExecutedContext.ActionDescriptor, + handlerExecutedContext = handlerExecutedContext, + filter = filter + }); + } + } + + public static void AfterOnPageHandlerExecuted( + this DiagnosticSource diagnosticSource, + PageHandlerExecutedContext handlerExecutedContext, + IPageFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(handlerExecutedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecuted")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnPageHandlerExecuted", + new + { + actionDescriptor = handlerExecutedContext.ActionDescriptor, + handlerExecutedContext = handlerExecutedContext, + filter = filter + }); + } + } + + public static void BeforeOnPageHandlerSelection( + this DiagnosticSource diagnosticSource, + PageHandlerSelectedContext handlerSelectedContext, + IAsyncPageFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(handlerSelectedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerSelection")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerSelection", + new + { + actionDescriptor = handlerSelectedContext.ActionDescriptor, + handlerSelectedContext = handlerSelectedContext, + filter = filter + }); + } + } + + public static void AfterOnPageHandlerSelection( + this DiagnosticSource diagnosticSource, + PageHandlerSelectedContext handlerSelectedContext, + IAsyncPageFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(handlerSelectedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnPageHandlerSelection")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnPageHandlerSelection", + new + { + actionDescriptor = handlerSelectedContext.ActionDescriptor, + handlerSelectedContext = handlerSelectedContext, + filter = filter + }); + } + } + + public static void BeforeOnPageHandlerSelected( + this DiagnosticSource diagnosticSource, + PageHandlerSelectedContext handlerSelectedContext, + IPageFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(handlerSelectedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerSelected")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.BeforeOnPageHandlerSelected", + new + { + actionDescriptor = handlerSelectedContext.ActionDescriptor, + handlerSelectedContext = handlerSelectedContext, + filter = filter + }); + } + } + + public static void AfterOnPageHandlerSelected( + this DiagnosticSource diagnosticSource, + PageHandlerSelectedContext handlerSelectedContext, + IPageFilter filter) + { + Debug.Assert(diagnosticSource != null); + Debug.Assert(handlerSelectedContext != null); + Debug.Assert(filter != null); + + if (diagnosticSource.IsEnabled("Microsoft.AspNetCore.Mvc.AfterOnPageHandlerSelected")) + { + diagnosticSource.Write( + "Microsoft.AspNetCore.Mvc.AfterOnPageHandlerSelected", + new + { + actionDescriptor = handlerSelectedContext.ActionDescriptor, + handlerSelectedContext = handlerSelectedContext, + filter = filter + }); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionDescriptorChangeProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionDescriptorChangeProvider.cs new file mode 100644 index 0000000000..c68ee3ed65 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionDescriptorChangeProvider.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; +using System.Diagnostics; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Razor.Internal; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class PageActionDescriptorChangeProvider : IActionDescriptorChangeProvider + { + private readonly IFileProvider _fileProvider; + private readonly string[] _searchPatterns; + private readonly string[] _additionalFilesToTrack; + + public PageActionDescriptorChangeProvider( + RazorTemplateEngine templateEngine, + IRazorViewEngineFileProviderAccessor fileProviderAccessor, + IOptions razorPagesOptions) + { + if (templateEngine == null) + { + throw new ArgumentNullException(nameof(templateEngine)); + } + + if (fileProviderAccessor == null) + { + throw new ArgumentNullException(nameof(fileProviderAccessor)); + } + + if (razorPagesOptions == null) + { + throw new ArgumentNullException(nameof(razorPagesOptions)); + } + + _fileProvider = fileProviderAccessor.FileProvider; + + var rootDirectory = razorPagesOptions.Value.RootDirectory; + Debug.Assert(!string.IsNullOrEmpty(rootDirectory)); + rootDirectory = rootDirectory.TrimEnd('/'); + + // Search pattern that matches all cshtml files under the Pages RootDirectory + var pagesRootSearchPattern = rootDirectory + "/**/*.cshtml"; + + // pagesRootSearchPattern will miss _ViewImports outside the RootDirectory despite these influencing + // compilation. e.g. when RootDirectory = /Dir1/Dir2, the search pattern will ignore changes to + // [/_ViewImports.cshtml, /Dir1/_ViewImports.cshtml]. We need to additionally account for these. + var importFileAtPagesRoot = rootDirectory + "/" + templateEngine.Options.ImportsFileName; + var additionalImportFilePaths = templateEngine.GetImportItems(importFileAtPagesRoot) + .Select(item => item.FilePath); + + if (razorPagesOptions.Value.AllowAreas) + { + // Search pattern that matches all cshtml files under the Pages AreaRootDirectory + var areaRootSearchPattern = "/Areas/**/*.cshtml"; + + var importFileAtAreaPagesRoot = $"/Areas/{templateEngine.Options.ImportsFileName}"; + var importPathsOutsideAreaPagesRoot = templateEngine.GetImportItems(importFileAtAreaPagesRoot) + .Select(item => item.FilePath); + + additionalImportFilePaths = additionalImportFilePaths + .Concat(importPathsOutsideAreaPagesRoot) + .Distinct(StringComparer.OrdinalIgnoreCase); + + _searchPatterns = new[] + { + pagesRootSearchPattern, + areaRootSearchPattern + }; + } + else + { + _searchPatterns = new[] { pagesRootSearchPattern, }; + } + + _additionalFilesToTrack = additionalImportFilePaths.ToArray(); + } + + public IChangeToken GetChangeToken() + { + var changeTokens = new IChangeToken[_additionalFilesToTrack.Length + _searchPatterns.Length]; + for (var i = 0; i < _additionalFilesToTrack.Length; i++) + { + changeTokens[i] = _fileProvider.Watch(_additionalFilesToTrack[i]); + } + + for (var i = 0; i < _searchPatterns.Length; i++) + { + var wildcardChangeToken = _fileProvider.Watch(_searchPatterns[i]); + changeTokens[_additionalFilesToTrack.Length + i] = wildcardChangeToken; + } + + return new CompositeChangeToken(changeTokens); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs new file mode 100644 index 0000000000..9684c711f0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs @@ -0,0 +1,723 @@ +// Copyright (c) .NET 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; +using System.Runtime.ExceptionServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class PageActionInvoker : ResourceInvoker, IActionInvoker + { + private readonly IPageHandlerMethodSelector _selector; + private readonly PageContext _pageContext; + private readonly ParameterBinder _parameterBinder; + private readonly ITempDataDictionaryFactory _tempDataFactory; + private readonly HtmlHelperOptions _htmlHelperOptions; + private readonly CompiledPageActionDescriptor _actionDescriptor; + + private Dictionary _arguments; + private HandlerMethodDescriptor _handler; + private PageBase _page; + private object _pageModel; + private ViewContext _viewContext; + + private PageHandlerSelectedContext _handlerSelectedContext; + private PageHandlerExecutingContext _handlerExecutingContext; + private PageHandlerExecutedContext _handlerExecutedContext; + + public PageActionInvoker( + IPageHandlerMethodSelector handlerMethodSelector, + DiagnosticSource diagnosticSource, + ILogger logger, + IActionResultTypeMapper mapper, + PageContext pageContext, + IFilterMetadata[] filterMetadata, + PageActionInvokerCacheEntry cacheEntry, + ParameterBinder parameterBinder, + ITempDataDictionaryFactory tempDataFactory, + HtmlHelperOptions htmlHelperOptions) + : base( + diagnosticSource, + logger, + mapper, + pageContext, + filterMetadata, + pageContext.ValueProviderFactories) + { + _selector = handlerMethodSelector; + _pageContext = pageContext; + CacheEntry = cacheEntry; + _parameterBinder = parameterBinder; + _tempDataFactory = tempDataFactory; + _htmlHelperOptions = htmlHelperOptions; + + _actionDescriptor = pageContext.ActionDescriptor; + } + + // Internal for testing + internal PageActionInvokerCacheEntry CacheEntry { get; } + + private bool HasPageModel => _actionDescriptor.HandlerTypeInfo != _actionDescriptor.PageTypeInfo; + + // Internal for testing + internal PageContext PageContext => _pageContext; + + /// + /// for details on what the variables in this method represent. + /// + protected override async Task InvokeInnerFilterAsync() + { + var next = State.PageBegin; + var scope = Scope.Invoker; + var state = (object)null; + var isCompleted = false; + + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); + } + } + + protected override void ReleaseResources() + { + if (_pageModel != null && CacheEntry.ReleaseModel != null) + { + CacheEntry.ReleaseModel(_pageContext, _pageModel); + } + + if (_page != null && CacheEntry.ReleasePage != null) + { + CacheEntry.ReleasePage(_pageContext, _viewContext, _page); + } + } + + protected override Task InvokeResultAsync(IActionResult result) + { + // We also have some special initialization we need to do for PageResult. + if (result is PageResult pageResult) + { + // If we used a PageModel then the Page isn't initialized yet. + if (_viewContext == null) + { + _viewContext = new ViewContext( + _pageContext, + NullView.Instance, + _pageContext.ViewData, + _tempDataFactory.GetTempData(_pageContext.HttpContext), + TextWriter.Null, + _htmlHelperOptions); + _viewContext.ExecutingFilePath = _pageContext.ActionDescriptor.RelativePath; + } + + if (_page == null) + { + _page = (PageBase)CacheEntry.PageFactory(_pageContext, _viewContext); + } + pageResult.Page = _page; + pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData; + } + + return base.InvokeResultAsync(result); + } + + private object CreateInstance() + { + if (HasPageModel) + { + // Since this is a PageModel, we need to activate it, and then run a handler method on the model. + _pageModel = CacheEntry.ModelFactory(_pageContext); + _pageContext.ViewData.Model = _pageModel; + + return _pageModel; + } + else + { + // Since this is a Page without a PageModel, we need to create the Page before running a handler method. + _viewContext = new ViewContext( + _pageContext, + NullView.Instance, + _pageContext.ViewData, + _tempDataFactory.GetTempData(_pageContext.HttpContext), + TextWriter.Null, + _htmlHelperOptions); + _viewContext.ExecutingFilePath = _pageContext.ActionDescriptor.RelativePath; + + _page = (PageBase)CacheEntry.PageFactory(_pageContext, _viewContext); + + if (_actionDescriptor.ModelTypeInfo == _actionDescriptor.PageTypeInfo) + { + _pageContext.ViewData.Model = _page; + } + + return _page; + } + } + + private HandlerMethodDescriptor SelectHandler() + { + return _selector.Select(_pageContext); + } + + private Task BindArgumentsAsync() + { + // Perf: Avoid allocating async state machines where possible. We only need the state + // machine if you need to bind properties or arguments. + if (_actionDescriptor.BoundProperties.Count == 0 && (_handler == null || _handler.Parameters.Count == 0)) + { + return Task.CompletedTask; + } + + return BindArgumentsCoreAsync(); + } + + private async Task BindArgumentsCoreAsync() + { + await CacheEntry.PropertyBinder(_pageContext, _instance); + + if (_handler == null) + { + return; + } + + // We do two separate cache lookups, once for the binder and once for the executor. + // Reducing it to a single lookup requires a lot of code change with little value. + PageHandlerBinderDelegate handlerBinder = null; + for (var i = 0; i < _actionDescriptor.HandlerMethods.Count; i++) + { + if (object.ReferenceEquals(_handler, _actionDescriptor.HandlerMethods[i])) + { + handlerBinder = CacheEntry.HandlerBinders[i]; + break; + } + } + + await handlerBinder(_pageContext, _arguments); + } + + private static object[] PrepareArguments( + IDictionary argumentsInDictionary, + HandlerMethodDescriptor handler) + { + if (handler.Parameters.Count == 0) + { + return null; + } + + var arguments = new object[handler.Parameters.Count]; + for (var i = 0; i < arguments.Length; i++) + { + var parameter = handler.Parameters[i]; + + if (argumentsInDictionary.TryGetValue(parameter.ParameterInfo.Name, out var value)) + { + // Do nothing, already set the value. + } + else if (!ParameterDefaultValue.TryGetDefaultValue(parameter.ParameterInfo, out value) && + parameter.ParameterInfo.ParameterType.IsValueType) + { + value = Activator.CreateInstance(parameter.ParameterInfo.ParameterType); + } + + arguments[i] = value; + } + + return arguments; + } + + private async Task InvokeHandlerMethodAsync() + { + var handler = _handler; + if (_handler != null) + { + var arguments = PrepareArguments(_arguments, handler); + + PageHandlerExecutorDelegate executor = null; + for (var i = 0; i < _actionDescriptor.HandlerMethods.Count; i++) + { + if (object.ReferenceEquals(handler, _actionDescriptor.HandlerMethods[i])) + { + executor = CacheEntry.HandlerExecutors[i]; + break; + } + } + + Debug.Assert(executor != null, "We should always find a executor for a handler"); + + _diagnosticSource.BeforeHandlerMethod(_pageContext, handler, _arguments, _instance); + _logger.ExecutingHandlerMethod(_pageContext, handler, arguments); + + try + { + _result = await executor(_instance, arguments); + _logger.ExecutedHandlerMethod(_pageContext, handler, _result); + } + finally + { + _diagnosticSource.AfterHandlerMethod(_pageContext, handler, _arguments, _instance, _result); + } + } + + // Pages have an implicit 'return Page()' even without a handler method. + if (_result == null) + { + _result = new PageResult(); + } + + // Ensure ViewData is set on PageResult for backwards compatibility (For example, Identity UI accesses + // ViewData in a PageFilter's PageHandlerExecutedMethod) + if (_result is PageResult pageResult) + { + pageResult.ViewData = pageResult.ViewData ?? _pageContext.ViewData; + } + } + + private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) + { + switch (next) + { + case State.PageBegin: + { + _instance = CreateInstance(); + + goto case State.PageSelectHandlerBegin; + } + + case State.PageSelectHandlerBegin: + { + _cursor.Reset(); + + _handler = SelectHandler(); + + goto case State.PageSelectHandlerNext; + } + + case State.PageSelectHandlerNext: + + var currentSelector = _cursor.GetNextFilter(); + if (currentSelector.FilterAsync != null) + { + if (_handlerSelectedContext == null) + { + _handlerSelectedContext = new PageHandlerSelectedContext(_pageContext, _filters, _instance) + { + HandlerMethod = _handler, + }; + } + + state = currentSelector.FilterAsync; + goto case State.PageSelectHandlerAsyncBegin; + } + else if (currentSelector.Filter != null) + { + if (_handlerSelectedContext == null) + { + _handlerSelectedContext = new PageHandlerSelectedContext(_pageContext, _filters, _instance) + { + HandlerMethod = _handler, + }; + } + + state = currentSelector.Filter; + goto case State.PageSelectHandlerSync; + } + else + { + goto case State.PageSelectHandlerEnd; + } + + case State.PageSelectHandlerAsyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_handlerSelectedContext != null); + + var filter = (IAsyncPageFilter)state; + var handlerSelectedContext = _handlerSelectedContext; + + _diagnosticSource.BeforeOnPageHandlerSelection(handlerSelectedContext, filter); + _logger.BeforeExecutingMethodOnFilter( + PageLoggerExtensions.PageFilter, + nameof(IAsyncPageFilter.OnPageHandlerSelectionAsync), + filter); + + var task = filter.OnPageHandlerSelectionAsync(handlerSelectedContext); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.PageSelectHandlerAsyncEnd; + return task; + } + + goto case State.PageSelectHandlerAsyncEnd; + } + + case State.PageSelectHandlerAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_handlerSelectedContext != null); + + var filter = (IAsyncPageFilter)state; + + _diagnosticSource.AfterOnPageHandlerSelection(_handlerSelectedContext, filter); + _logger.AfterExecutingMethodOnFilter( + PageLoggerExtensions.PageFilter, + nameof(IAsyncPageFilter.OnPageHandlerSelectionAsync), + filter); + + goto case State.PageSelectHandlerNext; + } + + case State.PageSelectHandlerSync: + { + Debug.Assert(state != null); + Debug.Assert(_handlerSelectedContext != null); + + var filter = (IPageFilter)state; + var handlerSelectedContext = _handlerSelectedContext; + + _diagnosticSource.BeforeOnPageHandlerSelected(handlerSelectedContext, filter); + _logger.BeforeExecutingMethodOnFilter( + PageLoggerExtensions.PageFilter, + nameof(IPageFilter.OnPageHandlerSelected), + filter); + + filter.OnPageHandlerSelected(handlerSelectedContext); + + _diagnosticSource.AfterOnPageHandlerSelected(handlerSelectedContext, filter); + + goto case State.PageSelectHandlerNext; + } + + case State.PageSelectHandlerEnd: + { + if (_handlerSelectedContext != null) + { + _handler = _handlerSelectedContext.HandlerMethod; + } + + _arguments = new Dictionary(StringComparer.OrdinalIgnoreCase); + + _cursor.Reset(); + + var task = BindArgumentsAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.PageNext; + return task; + } + + goto case State.PageNext; + } + + case State.PageNext: + { + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) + { + if (_handlerExecutingContext == null) + { + _handlerExecutingContext = new PageHandlerExecutingContext(_pageContext, _filters, _handler, _arguments, _instance); + } + + state = current.FilterAsync; + goto case State.PageAsyncBegin; + } + else if (current.Filter != null) + { + if (_handlerExecutingContext == null) + { + _handlerExecutingContext = new PageHandlerExecutingContext(_pageContext, _filters, _handler, _arguments, _instance); + } + + state = current.Filter; + goto case State.PageSyncBegin; + } + else + { + goto case State.PageInside; + } + } + + case State.PageAsyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_handlerExecutingContext != null); + + var filter = (IAsyncPageFilter)state; + var handlerExecutingContext = _handlerExecutingContext; + + _diagnosticSource.BeforeOnPageHandlerExecution(handlerExecutingContext, filter); + _logger.BeforeExecutingMethodOnFilter( + PageLoggerExtensions.PageFilter, + nameof(IAsyncPageFilter.OnPageHandlerExecutionAsync), + filter); + + var task = filter.OnPageHandlerExecutionAsync(handlerExecutingContext, InvokeNextPageFilterAwaitedAsync); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.PageAsyncEnd; + return task; + } + + goto case State.PageAsyncEnd; + } + + case State.PageAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_handlerExecutingContext != null); + + var filter = (IAsyncPageFilter)state; + + if (_handlerExecutedContext == null) + { + // If we get here then the filter didn't call 'next' indicating a short circuit. + _logger.PageFilterShortCircuited(filter); + + _handlerExecutedContext = new PageHandlerExecutedContext( + _pageContext, + _filters, + _handler, + _instance) + { + Canceled = true, + Result = _handlerExecutingContext.Result, + }; + } + + _diagnosticSource.AfterOnPageHandlerExecution(_handlerExecutedContext, filter); + _logger.AfterExecutingMethodOnFilter( + PageLoggerExtensions.PageFilter, + nameof(IAsyncPageFilter.OnPageHandlerExecutionAsync), + filter); + + goto case State.PageEnd; + } + + case State.PageSyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_handlerExecutingContext != null); + + var filter = (IPageFilter)state; + var handlerExecutingContext = _handlerExecutingContext; + + _diagnosticSource.BeforeOnPageHandlerExecuting(handlerExecutingContext, filter); + _logger.BeforeExecutingMethodOnFilter( + PageLoggerExtensions.PageFilter, + nameof(IPageFilter.OnPageHandlerExecuting), + filter); + + filter.OnPageHandlerExecuting(handlerExecutingContext); + + _diagnosticSource.AfterOnPageHandlerExecuting(handlerExecutingContext, filter); + _logger.AfterExecutingMethodOnFilter( + PageLoggerExtensions.PageFilter, + nameof(IPageFilter.OnPageHandlerExecuting), + filter); + + if (handlerExecutingContext.Result != null) + { + // Short-circuited by setting a result. + _logger.PageFilterShortCircuited(filter); + + _handlerExecutedContext = new PageHandlerExecutedContext( + _pageContext, + _filters, + _handler, + _instance) + { + Canceled = true, + Result = _handlerExecutingContext.Result, + }; + + goto case State.PageEnd; + } + + var task = InvokeNextPageFilterAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.PageSyncEnd; + return task; + } + + goto case State.PageSyncEnd; + } + + case State.PageSyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_handlerExecutingContext != null); + Debug.Assert(_handlerExecutedContext != null); + + var filter = (IPageFilter)state; + var handlerExecutedContext = _handlerExecutedContext; + + _diagnosticSource.BeforeOnPageHandlerExecuted(handlerExecutedContext, filter); + _logger.BeforeExecutingMethodOnFilter( + PageLoggerExtensions.PageFilter, + nameof(IPageFilter.OnPageHandlerExecuted), + filter); + + filter.OnPageHandlerExecuted(handlerExecutedContext); + + _diagnosticSource.AfterOnPageHandlerExecuted(handlerExecutedContext, filter); + _logger.AfterExecutingMethodOnFilter( + PageLoggerExtensions.PageFilter, + nameof(IPageFilter.OnPageHandlerExecuted), + filter); + + goto case State.PageEnd; + } + + case State.PageInside: + { + var task = InvokeHandlerMethodAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.PageEnd; + return task; + } + + goto case State.PageEnd; + } + + case State.PageEnd: + { + if (scope == Scope.Page) + { + if (_handlerExecutedContext == null) + { + _handlerExecutedContext = new PageHandlerExecutedContext(_pageContext, _filters, _handler, _instance) + { + Result = _result, + }; + } + + isCompleted = true; + return Task.CompletedTask; + } + + var handlerExecutedContext = _handlerExecutedContext; + Rethrow(handlerExecutedContext); + + if (handlerExecutedContext != null) + { + _result = handlerExecutedContext.Result; + } + + isCompleted = true; + return Task.CompletedTask; + } + + default: + throw new InvalidOperationException(); + } + } + + private async Task InvokeNextPageFilterAsync() + { + try + { + var next = State.PageNext; + var state = (object)null; + var scope = Scope.Page; + var isCompleted = false; + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); + } + } + catch (Exception exception) + { + _handlerExecutedContext = new PageHandlerExecutedContext(_pageContext, _filters, _handler, _instance) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), + }; + } + + Debug.Assert(_handlerExecutedContext != null); + } + + private async Task InvokeNextPageFilterAwaitedAsync() + { + Debug.Assert(_handlerExecutingContext != null); + if (_handlerExecutingContext.Result != null) + { + // If we get here, it means that an async filter set a result AND called next(). This is forbidden. + var message = Resources.FormatAsyncPageFilter_InvalidShortCircuit( + typeof(IAsyncPageFilter).Name, + nameof(PageHandlerExecutingContext.Result), + typeof(PageHandlerExecutingContext).Name, + typeof(PageHandlerExecutionDelegate).Name); + + throw new InvalidOperationException(message); + } + + await InvokeNextPageFilterAsync(); + + Debug.Assert(_handlerExecutedContext != null); + return _handlerExecutedContext; + } + + private static void Rethrow(PageHandlerExecutedContext context) + { + if (context == null) + { + return; + } + + if (context.ExceptionHandled) + { + return; + } + + if (context.ExceptionDispatchInfo != null) + { + context.ExceptionDispatchInfo.Throw(); + } + + if (context.Exception != null) + { + throw context.Exception; + } + } + + private enum Scope + { + Invoker, + Page, + } + + private enum State + { + PageBegin, + PageSelectHandlerBegin, + PageSelectHandlerNext, + PageSelectHandlerAsyncBegin, + PageSelectHandlerAsyncEnd, + PageSelectHandlerSync, + PageSelectHandlerEnd, + PageNext, + PageAsyncBegin, + PageAsyncEnd, + PageSyncBegin, + PageSyncEnd, + PageInside, + PageEnd, + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs new file mode 100644 index 0000000000..f2844d2023 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET 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.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class PageActionInvokerCacheEntry + { + public PageActionInvokerCacheEntry( + CompiledPageActionDescriptor actionDescriptor, + Func viewDataFactory, + Func pageFactory, + Action releasePage, + Func modelFactory, + Action releaseModel, + Func propertyBinder, + PageHandlerExecutorDelegate[] handlerExecutors, + PageHandlerBinderDelegate[] handlerBinders, + IReadOnlyList> viewStartFactories, + FilterItem[] cacheableFilters) + { + ActionDescriptor = actionDescriptor; + ViewDataFactory = viewDataFactory; + PageFactory = pageFactory; + ReleasePage = releasePage; + ModelFactory = modelFactory; + ReleaseModel = releaseModel; + PropertyBinder = propertyBinder; + HandlerExecutors = handlerExecutors; + HandlerBinders = handlerBinders; + ViewStartFactories = viewStartFactories; + CacheableFilters = cacheableFilters; + } + + public CompiledPageActionDescriptor ActionDescriptor { get; } + + public Func PageFactory { get; } + + /// + /// The action invoked to release a page. This may be null. + /// + public Action ReleasePage { get; } + + public Func ModelFactory { get; } + + /// + /// The delegate invoked to release a model. This may be null. + /// + public Action ReleaseModel { get; } + + /// + /// The delegate invoked to bind either the handler type (page or model). + /// This may be null. + /// + public Func PropertyBinder { get; } + + public PageHandlerExecutorDelegate[] HandlerExecutors { get; } + + public PageHandlerBinderDelegate[] HandlerBinders { get; } + + public Func ViewDataFactory { get; } + + /// + /// Gets the applicable ViewStart pages. + /// + public IReadOnlyList> ViewStartFactories { get; } + + public FilterItem[] CacheableFilters { get; } + + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs new file mode 100644 index 0000000000..007a2f9b14 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs @@ -0,0 +1,289 @@ +// Copyright (c) .NET 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.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class PageActionInvokerProvider : IActionInvokerProvider + { + private const string ViewStartFileName = "_ViewStart.cshtml"; + + private readonly IPageLoader _loader; + private readonly IPageFactoryProvider _pageFactoryProvider; + private readonly IPageModelFactoryProvider _modelFactoryProvider; + private readonly IModelBinderFactory _modelBinderFactory; + private readonly IRazorPageFactoryProvider _razorPageFactoryProvider; + private readonly IActionDescriptorCollectionProvider _collectionProvider; + private readonly IFilterProvider[] _filterProviders; + private readonly IReadOnlyList _valueProviderFactories; + private readonly ParameterBinder _parameterBinder; + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly ITempDataDictionaryFactory _tempDataFactory; + private readonly MvcOptions _mvcOptions; + private readonly HtmlHelperOptions _htmlHelperOptions; + private readonly IPageHandlerMethodSelector _selector; + private readonly RazorProjectFileSystem _razorFileSystem; + private readonly DiagnosticSource _diagnosticSource; + private readonly ILogger _logger; + private readonly IActionResultTypeMapper _mapper; + private volatile InnerCache _currentCache; + + public PageActionInvokerProvider( + IPageLoader loader, + IPageFactoryProvider pageFactoryProvider, + IPageModelFactoryProvider modelFactoryProvider, + IRazorPageFactoryProvider razorPageFactoryProvider, + IActionDescriptorCollectionProvider collectionProvider, + IEnumerable filterProviders, + ParameterBinder parameterBinder, + IModelMetadataProvider modelMetadataProvider, + IModelBinderFactory modelBinderFactory, + ITempDataDictionaryFactory tempDataFactory, + IOptions mvcOptions, + IOptions htmlHelperOptions, + IPageHandlerMethodSelector selector, + RazorProjectFileSystem razorFileSystem, + DiagnosticSource diagnosticSource, + ILoggerFactory loggerFactory, + IActionResultTypeMapper mapper) + { + _loader = loader; + _pageFactoryProvider = pageFactoryProvider; + _modelFactoryProvider = modelFactoryProvider; + _modelBinderFactory = modelBinderFactory; + _razorPageFactoryProvider = razorPageFactoryProvider; + _collectionProvider = collectionProvider; + _filterProviders = filterProviders.ToArray(); + _valueProviderFactories = mvcOptions.Value.ValueProviderFactories.ToArray(); + _parameterBinder = parameterBinder; + _modelMetadataProvider = modelMetadataProvider; + _tempDataFactory = tempDataFactory; + _mvcOptions = mvcOptions.Value; + _htmlHelperOptions = htmlHelperOptions.Value; + _selector = selector; + _razorFileSystem = razorFileSystem; + _diagnosticSource = diagnosticSource; + _logger = loggerFactory.CreateLogger(); + _mapper = mapper; + } + + public int Order { get; } = -1000; + + public void OnProvidersExecuting(ActionInvokerProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var actionContext = context.ActionContext; + var actionDescriptor = actionContext.ActionDescriptor as PageActionDescriptor; + if (actionDescriptor == null) + { + return; + } + + var cache = CurrentCache; + IFilterMetadata[] filters; + if (!cache.Entries.TryGetValue(actionDescriptor, out var cacheEntry)) + { + actionContext.ActionDescriptor = _loader.Load(actionDescriptor); + + var filterFactoryResult = FilterFactory.GetAllFilters(_filterProviders, actionContext); + filters = filterFactoryResult.Filters; + cacheEntry = CreateCacheEntry(context, filterFactoryResult.CacheableFilters); + cacheEntry = cache.Entries.GetOrAdd(actionDescriptor, cacheEntry); + } + else + { + filters = FilterFactory.CreateUncachedFilters( + _filterProviders, + actionContext, + cacheEntry.CacheableFilters); + } + + context.Result = CreateActionInvoker(actionContext, cacheEntry, filters); + } + + public void OnProvidersExecuted(ActionInvokerProviderContext context) + { + + } + + private InnerCache CurrentCache + { + get + { + var current = _currentCache; + var actionDescriptors = _collectionProvider.ActionDescriptors; + + if (current == null || current.Version != actionDescriptors.Version) + { + current = new InnerCache(actionDescriptors.Version); + _currentCache = current; + } + + return current; + } + } + + private PageActionInvoker CreateActionInvoker( + ActionContext actionContext, + PageActionInvokerCacheEntry cacheEntry, + IFilterMetadata[] filters) + { + var pageContext = new PageContext(actionContext) + { + ActionDescriptor = cacheEntry.ActionDescriptor, + ValueProviderFactories = new CopyOnWriteList(_valueProviderFactories), + ViewData = cacheEntry.ViewDataFactory(_modelMetadataProvider, actionContext.ModelState), + ViewStartFactories = cacheEntry.ViewStartFactories.ToList(), + }; + + return new PageActionInvoker( + _selector, + _diagnosticSource, + _logger, + _mapper, + pageContext, + filters, + cacheEntry, + _parameterBinder, + _tempDataFactory, + _htmlHelperOptions); + } + + private PageActionInvokerCacheEntry CreateCacheEntry( + ActionInvokerProviderContext context, + FilterItem[] cachedFilters) + { + var compiledActionDescriptor = (CompiledPageActionDescriptor)context.ActionContext.ActionDescriptor; + + var viewDataFactory = ViewDataDictionaryFactory.CreateFactory(compiledActionDescriptor.DeclaredModelTypeInfo); + + var pageFactory = _pageFactoryProvider.CreatePageFactory(compiledActionDescriptor); + var pageDisposer = _pageFactoryProvider.CreatePageDisposer(compiledActionDescriptor); + var propertyBinder = PageBinderFactory.CreatePropertyBinder( + _parameterBinder, + _modelMetadataProvider, + _modelBinderFactory, + compiledActionDescriptor); + + Func modelFactory = null; + Action modelReleaser = null; + if (compiledActionDescriptor.ModelTypeInfo != compiledActionDescriptor.PageTypeInfo) + { + modelFactory = _modelFactoryProvider.CreateModelFactory(compiledActionDescriptor); + modelReleaser = _modelFactoryProvider.CreateModelDisposer(compiledActionDescriptor); + } + + var viewStartFactories = GetViewStartFactories(compiledActionDescriptor); + + var handlerExecutors = GetHandlerExecutors(compiledActionDescriptor); + var handlerBinders = GetHandlerBinders(compiledActionDescriptor); + + return new PageActionInvokerCacheEntry( + compiledActionDescriptor, + viewDataFactory, + pageFactory, + pageDisposer, + modelFactory, + modelReleaser, + propertyBinder, + handlerExecutors, + handlerBinders, + viewStartFactories, + cachedFilters); + } + + // Internal for testing. + internal List> GetViewStartFactories(CompiledPageActionDescriptor descriptor) + { + var viewStartFactories = new List>(); + // Always pick up all _ViewStarts, including the ones outside the Pages root. + var viewStartItems = _razorFileSystem.FindHierarchicalItems( + descriptor.RelativePath, + ViewStartFileName); + foreach (var item in viewStartItems) + { + var factoryResult = _razorPageFactoryProvider.CreateFactory(item.FilePath); + if (factoryResult.Success) + { + viewStartFactories.Insert(0, factoryResult.RazorPageFactory); + } + } + + return viewStartFactories; + } + + private static PageHandlerExecutorDelegate[] GetHandlerExecutors(CompiledPageActionDescriptor actionDescriptor) + { + if (actionDescriptor.HandlerMethods == null || actionDescriptor.HandlerMethods.Count == 0) + { + return Array.Empty(); + } + + var results = new PageHandlerExecutorDelegate[actionDescriptor.HandlerMethods.Count]; + + for (var i = 0; i < actionDescriptor.HandlerMethods.Count; i++) + { + results[i] = ExecutorFactory.CreateExecutor(actionDescriptor.HandlerMethods[i]); + } + + return results; + } + + private PageHandlerBinderDelegate[] GetHandlerBinders(CompiledPageActionDescriptor actionDescriptor) + { + if (actionDescriptor.HandlerMethods == null ||actionDescriptor.HandlerMethods.Count == 0) + { + return Array.Empty(); + } + + var results = new PageHandlerBinderDelegate[actionDescriptor.HandlerMethods.Count]; + + for (var i = 0; i < actionDescriptor.HandlerMethods.Count; i++) + { + results[i] = PageBinderFactory.CreateHandlerBinder( + _parameterBinder, + _modelMetadataProvider, + _modelBinderFactory, + actionDescriptor, + actionDescriptor.HandlerMethods[i], + _mvcOptions); + } + + return results; + } + + internal class InnerCache + { + public InnerCache(int version) + { + Version = version; + } + + public ConcurrentDictionary Entries { get; } = + new ConcurrentDictionary(); + + public int Version { get; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageBinderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageBinderFactory.cs new file mode 100644 index 0000000000..6fcc9e08c8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageBinderFactory.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.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + internal static class PageBinderFactory + { + internal static readonly Func NullPropertyBinder = (context, arguments) => Task.CompletedTask; + internal static readonly PageHandlerBinderDelegate NullHandlerBinder = (context, arguments) => Task.CompletedTask; + + public static Func CreatePropertyBinder( + ParameterBinder parameterBinder, + IModelMetadataProvider modelMetadataProvider, + IModelBinderFactory modelBinderFactory, + CompiledPageActionDescriptor actionDescriptor) + { + if (parameterBinder == null) + { + throw new ArgumentNullException(nameof(parameterBinder)); + } + + if (actionDescriptor == null) + { + throw new ArgumentNullException(nameof(actionDescriptor)); + } + + var properties = actionDescriptor.BoundProperties; + if (properties == null || properties.Count == 0) + { + return NullPropertyBinder; + } + + var handlerType = actionDescriptor.HandlerTypeInfo.AsType(); + var propertyBindingInfo = new BinderItem[properties.Count]; + for (var i = 0; i < properties.Count; i++) + { + var property = properties[i]; + var metadata = modelMetadataProvider.GetMetadataForProperty(handlerType, property.Name); + var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext + { + BindingInfo = property.BindingInfo, + Metadata = metadata, + CacheToken = property, + }); + + propertyBindingInfo[i] = new BinderItem(binder, metadata); + } + + return Bind; + + async Task Bind(PageContext pageContext, object instance) + { + var valueProvider = await CompositeValueProvider.CreateAsync(pageContext, pageContext.ValueProviderFactories); + for (var i = 0; i < properties.Count; i++) + { + var property = properties[i]; + var bindingInfo = propertyBindingInfo[i]; + var modelMetadata = bindingInfo.ModelMetadata; + + if (!modelMetadata.IsBindingAllowed) + { + continue; + } + + var result = await parameterBinder.BindModelAsync( + pageContext, + bindingInfo.ModelBinder, + valueProvider, + property, + modelMetadata, + value: null); + + if (result.IsModelSet) + { + PropertyValueSetter.SetValue(bindingInfo.ModelMetadata, instance, result.Model); + } + } + } + } + + public static PageHandlerBinderDelegate CreateHandlerBinder( + ParameterBinder parameterBinder, + IModelMetadataProvider modelMetadataProvider, + IModelBinderFactory modelBinderFactory, + CompiledPageActionDescriptor actionDescriptor, + HandlerMethodDescriptor handler, + MvcOptions mvcOptions) + { + if (handler.Parameters == null || handler.Parameters.Count == 0) + { + return NullHandlerBinder; + } + + var handlerType = actionDescriptor.HandlerTypeInfo.AsType(); + var parameterBindingInfo = new BinderItem[handler.Parameters.Count]; + for (var i = 0; i < parameterBindingInfo.Length; i++) + { + var parameter = handler.Parameters[i]; + ModelMetadata metadata; + if (mvcOptions.AllowValidatingTopLevelNodes && + modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase) + { + // The default model metadata provider derives from ModelMetadataProvider + // and can therefore supply information about attributes applied to parameters. + metadata = modelMetadataProviderBase.GetMetadataForParameter(parameter.ParameterInfo); + } + else + { + // For backward compatibility, if there's a custom model metadata provider that + // only implements the older IModelMetadataProvider interface, access the more + // limited metadata information it supplies. In this scenario, validation attributes + // are not supported on parameters. + metadata = modelMetadataProvider.GetMetadataForType(parameter.ParameterType); + } + + var binder = modelBinderFactory.CreateBinder(new ModelBinderFactoryContext + { + BindingInfo = parameter.BindingInfo, + Metadata = metadata, + CacheToken = parameter, + }); + + parameterBindingInfo[i] = new BinderItem(binder, metadata); + } + + return Bind; + + async Task Bind(PageContext pageContext, IDictionary arguments) + { + var valueProvider = await CompositeValueProvider.CreateAsync(pageContext, pageContext.ValueProviderFactories); + + for (var i = 0; i < parameterBindingInfo.Length; i++) + { + var parameter = handler.Parameters[i]; + var bindingInfo = parameterBindingInfo[i]; + var modelMetadata = bindingInfo.ModelMetadata; + + if (!modelMetadata.IsBindingAllowed) + { + continue; + } + + var result = await parameterBinder.BindModelAsync( + pageContext, + bindingInfo.ModelBinder, + valueProvider, + parameter, + modelMetadata, + value: null); + + if (result.IsModelSet) + { + arguments[parameter.Name] = result.Model; + } + } + } + } + + private struct BinderItem + { + public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata) + { + ModelMetadata = modelMetadata; + ModelBinder = modelBinder; + } + + public ModelMetadata ModelMetadata { get; } + + public IModelBinder ModelBinder { get; } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerBinderDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerBinderDelegate.cs new file mode 100644 index 0000000000..7bea47fc51 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerBinderDelegate.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. + +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public delegate Task PageHandlerBinderDelegate(PageContext pageContext, IDictionary arguments); +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerExecutorDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerExecutorDelegate.cs new file mode 100644 index 0000000000..e90c24dc75 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerExecutorDelegate.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. + +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public delegate Task PageHandlerExecutorDelegate(object handler, object[] arguments); +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerPageFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerPageFilter.cs new file mode 100644 index 0000000000..662fa6113a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerPageFilter.cs @@ -0,0 +1,82 @@ +// Copyright (c) .NET 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.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class PageHandlerPageFilter : IAsyncPageFilter, IOrderedFilter + { + /// + /// Filters on handlers run furthest from the action. + /// t + public int Order => int.MinValue; + + public Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + + var handlerInstance = context.HandlerInstance; + if (handlerInstance == null) + { + throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(context.HandlerInstance), + nameof(PageHandlerExecutedContext))); + } + + if (handlerInstance is IAsyncPageFilter asyncPageFilter) + { + return asyncPageFilter.OnPageHandlerExecutionAsync(context, next); + } + else if (handlerInstance is IPageFilter pageFilter) + { + return ExecuteSyncFilter(context, next, pageFilter); + } + else + { + return next(); + } + } + + public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.HandlerInstance is IAsyncPageFilter asyncPageFilter) + { + return asyncPageFilter.OnPageHandlerSelectionAsync(context); + } + else if (context.HandlerInstance is IPageFilter pageFilter) + { + pageFilter.OnPageHandlerSelected(context); + } + + return Task.CompletedTask; + } + + private static async Task ExecuteSyncFilter( + PageHandlerExecutingContext context, + PageHandlerExecutionDelegate next, + IPageFilter pageFilter) + { + pageFilter.OnPageHandlerExecuting(context); + if (context.Result == null) + { + pageFilter.OnPageHandlerExecuted(await next()); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerResultFIlter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerResultFIlter.cs new file mode 100644 index 0000000000..9d0b3dcd09 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerResultFIlter.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; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class PageHandlerResultFilter : IAsyncResultFilter, IOrderedFilter + { + /// + /// Filters on handlers run furthest from the action. + /// + public int Order => int.MinValue; + + public Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + + var handler = context.Controller; + if (handler == null) + { + throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(context.Controller), + nameof(ResultExecutingContext))); + } + + if (handler is IAsyncResultFilter asyncResultFilter) + { + return asyncResultFilter.OnResultExecutionAsync(context, next); + } + else if (handler is IResultFilter resultFilter) + { + return ExecuteSyncFilter(context, next, resultFilter); + } + else + { + return next(); + } + } + + private static async Task ExecuteSyncFilter( + ResultExecutingContext context, + ResultExecutionDelegate next, + IResultFilter resultFilter) + { + resultFilter.OnResultExecuting(context); + if (!context.Cancel) + { + resultFilter.OnResultExecuted(await next()); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageLoggerExtensions.cs new file mode 100644 index 0000000000..a5d7c960d4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageLoggerExtensions.cs @@ -0,0 +1,151 @@ +// Copyright (c) .NET 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.ModelBinding; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + internal static class PageLoggerExtensions + { + public const string PageFilter = "Page Filter"; + + private static readonly Action _handlerMethodExecuting; + private static readonly Action _handlerMethodExecuted; + private static readonly Action _pageFilterShortCircuit; + private static readonly Action _malformedPageDirective; + private static readonly Action _unsupportedAreaPath; + private static readonly Action _notMostEffectiveFilter; + private static readonly Action _beforeExecutingMethodOnFilter; + private static readonly Action _afterExecutingMethodOnFilter; + + static PageLoggerExtensions() + { + // These numbers start at 101 intentionally to avoid conflict with the IDs used by ResourceInvoker. + + _handlerMethodExecuting = LoggerMessage.Define( + LogLevel.Information, + 101, + "Executing handler method {HandlerName} with arguments ({Arguments}) - ModelState is {ValidationState}"); + + _handlerMethodExecuted = LoggerMessage.Define( + LogLevel.Debug, + 102, + "Executed handler method {HandlerName}, returned result {ActionResult}."); + + _pageFilterShortCircuit = LoggerMessage.Define( + LogLevel.Debug, + 3, + "Request was short circuited at page filter '{PageFilter}'."); + + _malformedPageDirective = LoggerMessage.Define( + LogLevel.Warning, + 104, + "The page directive at '{FilePath}' is malformed. Please fix the following issues: {Diagnostics}"); + + _notMostEffectiveFilter = LoggerMessage.Define( + LogLevel.Debug, + 1, + "Skipping the execution of current filter as its not the most effective filter implementing the policy {FilterPolicy}."); + + _beforeExecutingMethodOnFilter = LoggerMessage.Define( + LogLevel.Trace, + 1, + "{FilterType}: Before executing {Method} on filter {Filter}."); + + _afterExecutingMethodOnFilter = LoggerMessage.Define( + LogLevel.Trace, + 2, + "{FilterType}: After executing {Method} on filter {Filter}."); + + _unsupportedAreaPath = LoggerMessage.Define( + LogLevel.Warning, + 1, + "The page at '{FilePath}' is located under the area root directory '/Areas/' but does not follow the path format '/Areas/AreaName/Pages/Directory/FileName.cshtml"); + } + + public static void ExecutingHandlerMethod(this ILogger logger, PageContext context, HandlerMethodDescriptor handler, object[] arguments) + { + if (logger.IsEnabled(LogLevel.Information)) + { + var handlerName = handler.MethodInfo.Name; + + string[] convertedArguments; + if (arguments == null) + { + convertedArguments = null; + } + else + { + convertedArguments = new string[arguments.Length]; + for (var i = 0; i < arguments.Length; i++) + { + convertedArguments[i] = Convert.ToString(arguments[i]); + } + } + + var validationState = context.ModelState.ValidationState; + + _handlerMethodExecuting(logger, handlerName, convertedArguments, validationState, null); + } + } + + public static void ExecutedHandlerMethod(this ILogger logger, PageContext context, HandlerMethodDescriptor handler, IActionResult result) + { + if (logger.IsEnabled(LogLevel.Debug)) + { + var handlerName = handler.MethodInfo.Name; + _handlerMethodExecuted(logger, handlerName, 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); + } + + public static void AfterExecutingMethodOnFilter(this ILogger logger, string filterType, string methodName, IFilterMetadata filter) + { + _afterExecutingMethodOnFilter(logger, filterType, methodName, filter.GetType().ToString(), null); + } + + public static void PageFilterShortCircuited( + this ILogger logger, + IFilterMetadata filter) + { + _pageFilterShortCircuit(logger, filter, null); + } + + public static void MalformedPageDirective(this ILogger logger, string filePath, IList diagnostics) + { + if (logger.IsEnabled(LogLevel.Warning)) + { + var messages = new string[diagnostics.Count]; + for (var i = 0; i < diagnostics.Count; i++) + { + messages[i] = diagnostics[i].GetMessage(); + } + + _malformedPageDirective(logger, filePath, messages, null); + } + } + + public static void NotMostEffectiveFilter(this ILogger logger, Type policyType) + { + _notMostEffectiveFilter(logger, policyType, null); + } + + public static void UnsupportedAreaPath(this ILogger logger, string filePath) + { + if (logger.IsEnabled(LogLevel.Warning)) + { + _unsupportedAreaPath(logger, filePath, null); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs new file mode 100644 index 0000000000..e3d2d6289a --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs @@ -0,0 +1,177 @@ +// Copyright (c) .NET 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 Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + internal class PageRouteModelFactory + { + private static readonly string IndexFileName = "Index" + RazorViewEngine.ViewExtension; + private readonly RazorPagesOptions _options; + private readonly ILogger _logger; + private readonly string _normalizedRootDirectory; + private readonly string _normalizedAreaRootDirectory; + + public PageRouteModelFactory( + RazorPagesOptions options, + ILogger logger) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + + _normalizedRootDirectory = NormalizeDirectory(options.RootDirectory); + _normalizedAreaRootDirectory = "/Areas/"; + } + + public PageRouteModel CreateRouteModel(string relativePath, string routeTemplate) + { + var viewEnginePath = GetViewEnginePath(_normalizedRootDirectory, relativePath); + var routeModel = new PageRouteModel(relativePath, viewEnginePath); + + PopulateRouteModel(routeModel, viewEnginePath, routeTemplate); + + return routeModel; + } + + public PageRouteModel CreateAreaRouteModel(string relativePath, string routeTemplate) + { + if (!TryParseAreaPath(relativePath, out var areaResult)) + { + return null; + } + + var routeModel = new PageRouteModel(relativePath, areaResult.viewEnginePath, areaResult.areaName); + + var routePrefix = CreateAreaRoute(areaResult.areaName, areaResult.viewEnginePath); + PopulateRouteModel(routeModel, routePrefix, routeTemplate); + routeModel.RouteValues["area"] = areaResult.areaName; + + return routeModel; + } + + private static void PopulateRouteModel(PageRouteModel model, string pageRoute, string routeTemplate) + { + model.RouteValues.Add("page", model.ViewEnginePath); + + var selectorModel = CreateSelectorModel(pageRoute, routeTemplate); + model.Selectors.Add(selectorModel); + + var fileName = Path.GetFileName(model.RelativePath); + if (!AttributeRouteModel.IsOverridePattern(routeTemplate) && + string.Equals(IndexFileName, fileName, StringComparison.OrdinalIgnoreCase)) + { + // For pages without an override route, and ending in /Index.cshtml, we want to allow + // incoming routing, but force outgoing routes to match to the path sans /Index. + selectorModel.AttributeRouteModel.SuppressLinkGeneration = true; + + var index = pageRoute.LastIndexOf('/'); + var parentDirectoryPath = index == -1 ? + string.Empty : + pageRoute.Substring(0, index); + model.Selectors.Add(CreateSelectorModel(parentDirectoryPath, routeTemplate)); + } + } + + // Internal for unit testing + internal bool TryParseAreaPath( + string relativePath, + out (string areaName, string viewEnginePath) result) + { + // path = "/Areas/Products/Pages/Manage/Home.cshtml" + // Result ("Products", "/Manage/Home") + const string AreaPagesRoot = "/Pages/"; + + result = default; + Debug.Assert(relativePath.StartsWith("/", StringComparison.Ordinal)); + // Parse the area root directory. + var areaRootEndIndex = relativePath.IndexOf('/', startIndex: 1); + if (areaRootEndIndex == -1 || + areaRootEndIndex >= relativePath.Length - 1 || // There's at least one token after the area root. + !relativePath.StartsWith(_normalizedAreaRootDirectory, StringComparison.OrdinalIgnoreCase)) // The path must start with area root. + { + _logger.UnsupportedAreaPath(relativePath); + return false; + } + + // The first directory that follows the area root is the area name. + var areaEndIndex = relativePath.IndexOf('/', startIndex: areaRootEndIndex + 1); + if (areaEndIndex == -1 || areaEndIndex == relativePath.Length) + { + _logger.UnsupportedAreaPath(relativePath); + return false; + } + + var areaName = relativePath.Substring(areaRootEndIndex + 1, areaEndIndex - areaRootEndIndex - 1); + // Ensure the next token is the "Pages" directory + if (string.Compare(relativePath, areaEndIndex, AreaPagesRoot, 0, AreaPagesRoot.Length, StringComparison.OrdinalIgnoreCase) != 0) + { + _logger.UnsupportedAreaPath(relativePath); + return false; + } + + // Include the trailing slash of the root directory at the start of the viewEnginePath + var pageNameIndex = areaEndIndex + AreaPagesRoot.Length - 1; + var viewEnginePath = relativePath.Substring(pageNameIndex, relativePath.Length - pageNameIndex - RazorViewEngine.ViewExtension.Length); + + result = (areaName, viewEnginePath); + return true; + } + + private string GetViewEnginePath(string rootDirectory, string path) + { + // rootDirectory = "/Pages/AllMyPages/" + // path = "/Pages/AllMyPages/Home.cshtml" + // Result = "/Home" + Debug.Assert(path.StartsWith(rootDirectory, StringComparison.OrdinalIgnoreCase)); + Debug.Assert(path.EndsWith(RazorViewEngine.ViewExtension, StringComparison.OrdinalIgnoreCase)); + var startIndex = rootDirectory.Length - 1; + var endIndex = path.Length - RazorViewEngine.ViewExtension.Length; + return path.Substring(startIndex, endIndex - startIndex); + } + + private static string CreateAreaRoute(string areaName, string viewEnginePath) + { + // AreaName = Products, ViewEnginePath = /List/Categories + // Result = /Products/List/Categories + Debug.Assert(!string.IsNullOrEmpty(areaName)); + Debug.Assert(!string.IsNullOrEmpty(viewEnginePath)); + Debug.Assert(viewEnginePath.StartsWith("/", StringComparison.Ordinal)); + + var builder = new InplaceStringBuilder(1 + areaName.Length + viewEnginePath.Length); + builder.Append('/'); + builder.Append(areaName); + builder.Append(viewEnginePath); + + return builder.ToString(); + } + + private static SelectorModel CreateSelectorModel(string prefix, string routeTemplate) + { + return new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel + { + Template = AttributeRouteModel.CombineTemplates(prefix, routeTemplate), + } + }; + } + + private static string NormalizeDirectory(string directory) + { + Debug.Assert(directory.StartsWith("/", StringComparison.Ordinal)); + if (directory.Length > 1 && !directory.EndsWith("/", StringComparison.Ordinal)) + { + return directory + "/"; + } + + return directory; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilter.cs new file mode 100644 index 0000000000..73633bf59c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilter.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.Filters; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + internal class PageSaveTempDataPropertyFilter : SaveTempDataPropertyFilterBase, IPageFilter + { + public PageSaveTempDataPropertyFilter(ITempDataDictionaryFactory factory) + : base(factory) + { + } + + public void OnPageHandlerSelected(PageHandlerSelectedContext context) + { + } + + public void OnPageHandlerExecuting(PageHandlerExecutingContext context) + { + Subject = context.HandlerInstance; + var tempData = _tempDataFactory.GetTempData(context.HttpContext); + + SetPropertyVaules(tempData); + } + + public void OnPageHandlerExecuted(PageHandlerExecutedContext context) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilterFactory.cs new file mode 100644 index 0000000000..b50ce6b2a9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilterFactory.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; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + internal class PageSaveTempDataPropertyFilterFactory : IFilterFactory + { + + public PageSaveTempDataPropertyFilterFactory(IReadOnlyList properties) + { + Properties = properties; + } + + public IReadOnlyList Properties { get; } + + public bool IsReusable => false; + + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + var service = serviceProvider.GetRequiredService(); + service.Properties = Properties; + + return service; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilter.cs new file mode 100644 index 0000000000..1199f1a91e --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilter.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.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + internal class PageViewDataAttributeFilter : IPageFilter, IViewDataValuesProviderFeature + { + public PageViewDataAttributeFilter(IReadOnlyList properties) + { + Properties = properties; + } + + public IReadOnlyList Properties { get; } + + public object Subject { get; set; } + + public void OnPageHandlerExecuted(PageHandlerExecutedContext context) + { + } + + public void OnPageHandlerExecuting(PageHandlerExecutingContext context) + { + Subject = context.HandlerInstance; + context.HttpContext.Features.Set(this); + } + + public void OnPageHandlerSelected(PageHandlerSelectedContext context) + { + } + + public void ProvideViewDataValues(ViewDataDictionary viewData) + { + for (var i = 0; i < Properties.Count; i++) + { + var property = Properties[i]; + var value = property.GetValue(Subject); + + if (value != null) + { + viewData[property.Key] = value; + } + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilterFactory.cs new file mode 100644 index 0000000000..e75f518aec --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilterFactory.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 System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + internal class PageViewDataAttributeFilterFactory : IFilterFactory + { + public PageViewDataAttributeFilterFactory(IReadOnlyList properties) + { + Properties = properties; + } + + public IReadOnlyList Properties { get; } + + // PageViewDataAttributeFilter is stateful and cannot be reused. + public bool IsReusable => false; + + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + return new PageViewDataAttributeFilter(Properties); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorPagesRazorViewEngineOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorPagesRazorViewEngineOptionsSetup.cs new file mode 100644 index 0000000000..a0d19edf2f --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorPagesRazorViewEngineOptionsSetup.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; +using System.Diagnostics; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class RazorPagesRazorViewEngineOptionsSetup : IConfigureOptions + { + private readonly RazorPagesOptions _pagesOptions; + + public RazorPagesRazorViewEngineOptionsSetup(IOptions pagesOptions) + { + _pagesOptions = pagesOptions?.Value ?? throw new ArgumentNullException(nameof(pagesOptions)); + } + + public void Configure(RazorViewEngineOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + var rootDirectory = _pagesOptions.RootDirectory; + Debug.Assert(!string.IsNullOrEmpty(rootDirectory)); + var defaultPageSearchPath = CombinePath(rootDirectory, "{1}/{0}" + RazorViewEngine.ViewExtension); + options.PageViewLocationFormats.Add(defaultPageSearchPath); + + // /Pages/Shared/{0}.cshtml + var pagesSharedDirectory = CombinePath(rootDirectory, "Shared/{0}" + RazorViewEngine.ViewExtension); + options.PageViewLocationFormats.Add(pagesSharedDirectory); + + options.PageViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension); + + var areaDirectory = CombinePath("/Areas/", "{2}"); + // Areas/{2}/Pages/ + var areaPagesDirectory = CombinePath(areaDirectory, "/Pages/"); + + // Areas/{2}/Pages/{1}/{0}.cshtml + // Areas/{2}/Pages/Shared/{0}.cshtml + // Areas/{2}/Views/Shared/{0}.cshtml + // Pages/Shared/{0}.cshtml + // Views/Shared/{0}.cshtml + var areaSearchPath = CombinePath(areaPagesDirectory, "{1}/{0}" + RazorViewEngine.ViewExtension); + options.AreaPageViewLocationFormats.Add(areaSearchPath); + + var areaPagesSharedSearchPath = CombinePath(areaPagesDirectory, "Shared/{0}" + RazorViewEngine.ViewExtension); + options.AreaPageViewLocationFormats.Add(areaPagesSharedSearchPath); + + var areaViewsSharedSearchPath = CombinePath(areaDirectory, "Views/Shared/{0}" + RazorViewEngine.ViewExtension); + options.AreaPageViewLocationFormats.Add(areaViewsSharedSearchPath); + + options.AreaPageViewLocationFormats.Add(pagesSharedDirectory); + options.AreaPageViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension); + + options.ViewLocationFormats.Add(pagesSharedDirectory); + options.AreaViewLocationFormats.Add(pagesSharedDirectory); + + options.ViewLocationExpanders.Add(new PageViewLocationExpander()); + } + + private static string CombinePath(string path1, string path2) + { + if (path1.EndsWith("/", StringComparison.Ordinal) || path2.StartsWith("/", StringComparison.Ordinal)) + { + return path1 + path2; + } + else if (path1.EndsWith("/", StringComparison.Ordinal) && path2.StartsWith("/", StringComparison.Ordinal)) + { + return path1 + path2.Substring(1); + } + + return path1 + "/" + path2; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs new file mode 100644 index 0000000000..81cf7b6a42 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.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.Linq; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Razor.Language; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class RazorProjectPageRouteModelProvider : IPageRouteModelProvider + { + private const string AreaRootDirectory = "/Areas"; + private readonly RazorProjectFileSystem _razorFileSystem; + private readonly RazorPagesOptions _pagesOptions; + private readonly PageRouteModelFactory _routeModelFactory; + private readonly ILogger _logger; + + public RazorProjectPageRouteModelProvider( + RazorProjectFileSystem razorFileSystem, + IOptions pagesOptionsAccessor, + ILoggerFactory loggerFactory) + { + _razorFileSystem = razorFileSystem; + _pagesOptions = pagesOptionsAccessor.Value; + _logger = loggerFactory.CreateLogger(); + _routeModelFactory = new PageRouteModelFactory(_pagesOptions, _logger); + } + + /// + /// Ordered to execute after . + /// + public int Order => -1000 + 10; + + public void OnProvidersExecuted(PageRouteModelProviderContext context) + { + } + + public void OnProvidersExecuting(PageRouteModelProviderContext context) + { + // When RootDirectory and AreaRootDirectory overlap, e.g. RootDirectory = /, AreaRootDirectoryy = /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. + + if (_pagesOptions.AllowAreas) + { + AddAreaPageModels(context); + } + + AddPageModels(context); + } + + private void AddPageModels(PageRouteModelProviderContext context) + { + 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))) + { + // A route for this file was already registered either by the CompiledPageRouteModel or as an area route. + // by this provider. Skip registering an additional entry. + + // Note: We're comparing duplicates based on root-relative paths. This eliminates a page from being discovered + // by overlapping area and non-area routes where ViewEnginePath would be different. + continue; + } + + if (!PageDirectiveFeature.TryGetPageDirective(_logger, item, out var routeTemplate)) + { + // .cshtml pages without @page are not RazorPages. + continue; + } + + if (_pagesOptions.AllowAreas && relativePath.StartsWith(AreaRootDirectory, StringComparison.OrdinalIgnoreCase)) + { + // Ignore Razor pages that are under the area root directory when AllowAreas is enabled. + // Conforming page paths will be added by AddAreaPageModels. + _logger.UnsupportedAreaPath(relativePath); + continue; + } + + var routeModel = _routeModelFactory.CreateRouteModel(relativePath, routeTemplate); + if (routeModel != null) + { + context.RouteModels.Add(routeModel); + } + } + } + + private void AddAreaPageModels(PageRouteModelProviderContext context) + { + 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))) + { + // A route for this file was already registered either by the CompiledPageRouteModel. + // Skip registering an additional entry. + continue; + } + + if (!PageDirectiveFeature.TryGetPageDirective(_logger, item, out var routeTemplate)) + { + // .cshtml pages without @page are not RazorPages. + continue; + } + + var routeModel = _routeModelFactory.CreateAreaRouteModel(relativePath, routeTemplate); + if (routeModel != null) + { + context.RouteModels.Add(routeModel); + } + } + } + + private static bool IsRouteable(RazorProjectItem item) + { + // Pages like _ViewImports should not be routable. + return !item.FileName.StartsWith("_", StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilter.cs new file mode 100644 index 0000000000..6fde160028 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilter.cs @@ -0,0 +1,108 @@ +// Copyright (c) .NET 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.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + /// + /// A which sets the appropriate headers related to response caching. + /// + public class ResponseCacheFilter : IPageFilter, IResponseCacheFilter + { + private readonly ResponseCacheFilterExecutor _executor; + private readonly ILogger _logger; + + /// + /// Creates a new instance of + /// + /// The profile which contains the settings for + /// . + /// The . + public ResponseCacheFilter(CacheProfile cacheProfile, ILoggerFactory loggerFactory) + { + _executor = new ResponseCacheFilterExecutor(cacheProfile); + _logger = loggerFactory.CreateLogger(GetType()); + } + + /// + /// Gets or sets the duration in seconds for which the response is cached. + /// This is a required parameter. + /// This sets "max-age" in "Cache-control" header. + /// + public int Duration + { + get => _executor.Duration; + set => _executor.Duration = value; + } + + /// + /// Gets or sets the location where the data from a particular URL must be cached. + /// + public ResponseCacheLocation Location + { + get => _executor.Location; + set => _executor.Location = value; + } + + /// + /// Gets or sets the value which determines whether the data should be stored or not. + /// When set to , it sets "Cache-control" header to "no-store". + /// Ignores the "Location" parameter for values other than "None". + /// Ignores the "duration" parameter. + /// + public bool NoStore + { + get => _executor.NoStore; + set => _executor.NoStore = value; + } + + /// + /// Gets or sets the value for the Vary response header. + /// + public string VaryByHeader + { + get => _executor.VaryByHeader; + set => _executor.VaryByHeader = value; + } + + /// + /// Gets or sets the query keys to vary by. + /// + /// + /// requires the response cache middleware. + /// + public string[] VaryByQueryKeys + { + get => _executor.VaryByQueryKeys; + set => _executor.VaryByQueryKeys = value; + } + + public void OnPageHandlerSelected(PageHandlerSelectedContext context) + { + } + + public void OnPageHandlerExecuting(PageHandlerExecutingContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (!context.IsEffectivePolicy(this)) + { + _logger.NotMostEffectiveFilter(typeof(IResponseCacheFilter)); + return; + } + + _executor.Execute(context); + } + + public void OnPageHandlerExecuted(PageHandlerExecutedContext context) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilterApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilterApplicationModelProvider.cs new file mode 100644 index 0000000000..27a8a7cb9b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilterApplicationModelProvider.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.Linq; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class ResponseCacheFilterApplicationModelProvider : IPageApplicationModelProvider + { + private readonly MvcOptions _mvcOptions; + private readonly ILoggerFactory _loggerFactory; + + public ResponseCacheFilterApplicationModelProvider(IOptions mvcOptionsAccessor, ILoggerFactory loggerFactory) + { + if (mvcOptionsAccessor == null) + { + throw new ArgumentNullException(nameof(mvcOptionsAccessor)); + } + + _mvcOptions = mvcOptionsAccessor.Value; + _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); + } + + // The order is set to execute after the DefaultPageApplicationModelProvider. + public int Order => -1000 + 10; + + public void OnProvidersExecuting(PageApplicationModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var pageModel = context.PageApplicationModel; + var responseCacheAttributes = pageModel.HandlerTypeAttributes.OfType(); + foreach (var attribute in responseCacheAttributes) + { + var cacheProfile = attribute.GetCacheProfile(_mvcOptions); + context.PageApplicationModel.Filters.Add(new ResponseCacheFilter(cacheProfile, _loggerFactory)); + } + } + + public void OnProvidersExecuted(PageApplicationModelProviderContext context) + { + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/TempDataFilterPageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/TempDataFilterPageApplicationModelProvider.cs new file mode 100644 index 0000000000..411c13c608 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/TempDataFilterPageApplicationModelProvider.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; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + internal class TempDataFilterPageApplicationModelProvider : IPageApplicationModelProvider + { + private readonly MvcViewOptions _options; + + public TempDataFilterPageApplicationModelProvider(IOptions options) + { + _options = options.Value; + } + + // 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; + var handlerType = pageApplicationModel.HandlerType.AsType(); + + var tempDataProperties = SaveTempDataPropertyFilterBase.GetTempDataProperties(handlerType, _options); + if (tempDataProperties == null) + { + return; + } + + var filter = new PageSaveTempDataPropertyFilterFactory(tempDataProperties); + pageApplicationModel.Filters.Add(filter); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ViewDataAttributePageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ViewDataAttributePageApplicationModelProvider.cs new file mode 100644 index 0000000000..cdfbe11f0c --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ViewDataAttributePageApplicationModelProvider.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.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.RazorPages.Internal; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + internal class ViewDataAttributePageApplicationModelProvider : IPageApplicationModelProvider + { + /// + /// This order ensures that runs after the . + public int Order => -1000 + 10; + + /// + public void OnProvidersExecuted(PageApplicationModelProviderContext context) + { + } + + /// + public void OnProvidersExecuting(PageApplicationModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var handlerType = context.PageApplicationModel.HandlerType.AsType(); + + var viewDataProperties = ViewDataAttributePropertyProvider.GetViewDataProperties(handlerType); + if (viewDataProperties == null) + { + return; + } + + var filter = new PageViewDataAttributeFilterFactory(viewDataProperties); + context.PageApplicationModel.Filters.Add(filter); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj new file mode 100644 index 0000000000..f0be433ea5 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj @@ -0,0 +1,25 @@ + + + + ASP.NET Core MVC Razor Pages. + netstandard2.0 + $(NoWarn);CS1591 + true + aspnetcore;aspnetcoremvc;cshtml;razor + + + + + + + + + + + + + + + + + diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/NonHandlerAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/NonHandlerAttribute.cs new file mode 100644 index 0000000000..5c9cf53994 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/NonHandlerAttribute.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.AspNetCore.Mvc.RazorPages +{ + /// + /// Specifies that the targeted method is not a page handler method. + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class NonHandlerAttribute : Attribute + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs new file mode 100644 index 0000000000..c6ee14940d --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.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.RazorPages +{ + /// + /// A base class for a Razor page. + /// + public abstract class Page : PageBase + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.cs new file mode 100644 index 0000000000..bf4780d165 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.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 System.Diagnostics; +using Microsoft.AspNetCore.Mvc.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + [DebuggerDisplay("{DebuggerDisplayString,nq}")] + public class PageActionDescriptor : ActionDescriptor + { + /// + /// Initializes a new instance of . + /// + public PageActionDescriptor() + { + } + + /// + /// A copy constructor for . + /// + /// The to copy from. + public PageActionDescriptor(PageActionDescriptor other) + { + if (other == null) + { + throw new ArgumentNullException(nameof(other)); + } + + RelativePath = other.RelativePath; + ViewEnginePath = other.ViewEnginePath; + AreaName = other.AreaName; + } + + /// + /// Gets or sets the application root relative path for the page. + /// + public string RelativePath { get; set; } + + /// + /// Gets or sets the path relative to the base path for page discovery. + /// + /// This value is the path of the file without extension, relative to the pages root directory. + /// e.g. the for the file /Pages/Catalog/Antiques.cshtml is /Catalog/Antiques + /// + /// + /// In an area, this value is the path of the file without extension, relative to the pages root directory for the specified area. + /// e.g. the for the file Areas/Identity/Pages/Manage/Accounts.cshtml, is /Manage/Accounts. + /// + /// + public string ViewEnginePath { get; set; } + + /// + /// Gets or sets the area name for this page. + /// This value will be null for non-area pages. + /// + public string AreaName { get; set; } + + /// + public override string DisplayName + { + get + { + if (base.DisplayName == null && ViewEnginePath != null) + { + base.DisplayName = ViewEnginePath; + } + + return base.DisplayName; + } + + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + base.DisplayName = value; + } + } + + private string DebuggerDisplayString() => $"{{ViewEnginePath = {nameof(ViewEnginePath)}, RelativePath = {nameof(RelativePath)}}}"; + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs new file mode 100644 index 0000000000..12b88777e8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs @@ -0,0 +1,1599 @@ +// Copyright (c) .NET 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.Expressions; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + /// + /// A base class for a Razor page. + /// + public abstract class PageBase : RazorPageBase + { + private IObjectModelValidator _objectValidator; + private IModelMetadataProvider _metadataProvider; + private IModelBinderFactory _modelBinderFactory; + + /// + /// The . + /// + public PageContext PageContext { get; set; } + + /// + public override ViewContext ViewContext { get; set; } + + /// + /// Gets the . + /// + public HttpContext HttpContext => PageContext?.HttpContext; + + /// + /// Gets the . + /// + public HttpRequest Request => HttpContext?.Request; + + /// + /// Gets the . + /// + public HttpResponse Response => HttpContext?.Response; + + /// + /// Gets the for the executing action. + /// + public RouteData RouteData => PageContext.RouteData; + + /// + /// Gets the . + /// + public ModelStateDictionary ModelState => PageContext?.ModelState; + + private IObjectModelValidator ObjectValidator + { + get + { + if (_objectValidator == null) + { + _objectValidator = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _objectValidator; + } + } + + private IModelMetadataProvider MetadataProvider + { + get + { + if (_metadataProvider == null) + { + _metadataProvider = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _metadataProvider; + } + } + + private IModelBinderFactory ModelBinderFactory + { + get + { + if (_modelBinderFactory == null) + { + _modelBinderFactory = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _modelBinderFactory; + } + } + + /// + public override void EnsureRenderedBodyOrSections() + { + // This will never be called by MVC. MVC only calls this method on layout pages, and a Page can never be a layout page. + throw new NotSupportedException(); + } + + /// + public override void BeginContext(int position, int length, bool isLiteral) + { + const string BeginContextEvent = "Microsoft.AspNetCore.Mvc.Razor.BeginInstrumentationContext"; + + if (DiagnosticSource?.IsEnabled(BeginContextEvent) == true) + { + DiagnosticSource.Write( + BeginContextEvent, + new + { + httpContext = ViewContext, + path = Path, + position = position, + length = length, + isLiteral = isLiteral, + }); + } + } + + /// + public override void EndContext() + { + const string EndContextEvent = "Microsoft.AspNetCore.Mvc.Razor.EndInstrumentationContext"; + + if (DiagnosticSource?.IsEnabled(EndContextEvent) == true) + { + DiagnosticSource.Write( + EndContextEvent, + new + { + httpContext = ViewContext, + path = Path, + }); + } + } + + /// + /// Creates a that produces a response. + /// + /// The created for the response. + [NonAction] + public virtual BadRequestResult BadRequest() + => new BadRequestResult(); + + /// + /// Creates a that produces a response. + /// + /// An error object to be returned to the client. + /// The created for the response. + [NonAction] + public virtual BadRequestObjectResult BadRequest(object error) + => new BadRequestObjectResult(error); + + /// + /// Creates a that produces a response. + /// + /// The containing errors to be returned to the client. + /// The created for the response. + [NonAction] + public virtual BadRequestObjectResult BadRequest(ModelStateDictionary modelState) + { + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + return new BadRequestObjectResult(modelState); + } + + /// + /// Creates a . + /// + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + public virtual ChallengeResult Challenge() + => new ChallengeResult(); + + /// + /// Creates a with the specified authentication schemes. + /// + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + public virtual ChallengeResult Challenge(params string[] authenticationSchemes) + => new ChallengeResult(authenticationSchemes); + + /// + /// Creates a with the specified . + /// + /// used to perform the authentication + /// challenge. + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + public virtual ChallengeResult Challenge(AuthenticationProperties properties) + => new ChallengeResult(properties); + + /// + /// Creates a with the specified authentication schemes and + /// . + /// + /// used to perform the authentication + /// challenge. + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + public virtual ChallengeResult Challenge( + AuthenticationProperties properties, + params string[] authenticationSchemes) + => new ChallengeResult(authenticationSchemes, properties); + + /// + /// Creates a object with by specifying a + /// string. + /// + /// The content to write to the response. + /// The created object for the response. + public virtual ContentResult Content(string content) + => Content(content, (MediaTypeHeaderValue)null); + + /// + /// Creates a object with by specifying a + /// string and a content type. + /// + /// The content to write to the response. + /// The content type (MIME type). + /// The created object for the response. + public virtual ContentResult Content(string content, string contentType) + => Content(content, MediaTypeHeaderValue.Parse(contentType)); + + /// + /// Creates a object with by specifying a + /// string, a , and . + /// + /// The content to write to the response. + /// The content type (MIME type). + /// The content encoding. + /// The created object for the response. + /// + /// If encoding is provided by both the 'charset' and the parameters, then + /// the parameter is chosen as the final encoding. + /// + public virtual ContentResult Content(string content, string contentType, Encoding contentEncoding) + { + var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType); + mediaTypeHeaderValue.Encoding = contentEncoding ?? mediaTypeHeaderValue.Encoding; + return Content(content, mediaTypeHeaderValue); + } + + /// + /// Creates a object with by specifying a + /// string and a . + /// + /// The content to write to the response. + /// The content type (MIME type). + /// The created object for the response. + public virtual ContentResult Content(string content, MediaTypeHeaderValue contentType) + { + return new ContentResult + { + Content = content, + ContentType = contentType?.ToString() + }; + } + + /// + /// Creates a ( by default). + /// + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + public virtual ForbidResult Forbid() + => new ForbidResult(); + + /// + /// Creates a ( by default) with the + /// specified authentication schemes. + /// + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + public virtual ForbidResult Forbid(params string[] authenticationSchemes) + => new ForbidResult(authenticationSchemes); + + /// + /// Creates a ( by default) with the + /// specified . + /// + /// used to perform the authentication + /// challenge. + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + public virtual ForbidResult Forbid(AuthenticationProperties properties) + => new ForbidResult(properties); + + /// + /// Creates a ( by default) with the + /// specified authentication schemes and . + /// + /// used to perform the authentication + /// challenge. + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + public virtual ForbidResult Forbid(AuthenticationProperties properties, params string[] authenticationSchemes) + => new ForbidResult(authenticationSchemes, properties); + + /// + /// Returns a file with the specified as content + /// () and the specified as the Content-Type. + /// + /// The file contents. + /// The Content-Type of the file. + /// The created for the response. + public virtual FileContentResult File(byte[] fileContents, string contentType) + => File(fileContents, contentType, fileDownloadName: null); + + /// + /// Returns a file with the specified as content (), the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// + /// The file contents. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + public virtual FileContentResult File(byte[] fileContents, string contentType, string fileDownloadName) + => new FileContentResult(fileContents, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Returns a file in the specified () + /// with the specified as the Content-Type. + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// The created for the response. + public virtual FileStreamResult File(Stream fileStream, string contentType) + => File(fileStream, contentType, fileDownloadName: null); + + /// + /// Returns a file in the specified () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + public virtual FileStreamResult File(Stream fileStream, string contentType, string fileDownloadName) + => new FileStreamResult(fileStream, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type. + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// The created for the response. + public virtual VirtualFileResult File(string virtualPath, string contentType) + => File(virtualPath, contentType, fileDownloadName: null); + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + public virtual VirtualFileResult File(string virtualPath, string contentType, string fileDownloadName) + => new VirtualFileResult(virtualPath, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type. + /// + /// The physical path of the file to be returned. + /// The Content-Type of the file. + /// The created for the response. + public virtual PhysicalFileResult PhysicalFile(string physicalPath, string contentType) + => PhysicalFile(physicalPath, contentType, fileDownloadName: null); + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// + /// The physical path of the file to be returned. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + public virtual PhysicalFileResult PhysicalFile( + string physicalPath, + string contentType, + string fileDownloadName) + => new PhysicalFileResult(physicalPath, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Creates a object that redirects + /// () to the specified local . + /// + /// The local URL to redirect to. + /// The created for the response. + public virtual LocalRedirectResult LocalRedirect(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl); + } + + /// + /// Creates a object with set to + /// true () using the specified . + /// + /// The local URL to redirect to. + /// The created for the response. + public virtual LocalRedirectResult LocalRedirectPermanent(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl, permanent: true); + } + + /// + /// Creates a object with set to + /// false and set to true + /// () using the specified . + /// + /// The local URL to redirect to. + /// The created for the response. + public virtual LocalRedirectResult LocalRedirectPreserveMethod(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl: localUrl, permanent: false, preserveMethod: true); + } + + /// + /// Creates a object with set to + /// true and set to true + /// () using the specified . + /// + /// The local URL to redirect to. + /// The created for the response. + public virtual LocalRedirectResult LocalRedirectPermanentPreserveMethod(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl: localUrl, permanent: true, preserveMethod: true); + } + + /// + /// Creates an that produces a response. + /// + /// The created for the response. + public virtual NotFoundResult NotFound() + => new NotFoundResult(); + + /// + /// Creates an that produces a response. + /// + /// The created for the response. + public virtual NotFoundObjectResult NotFound(object value) + => new NotFoundObjectResult(value); + + /// + /// Creates a object that renders this page as a view to the response. + /// + /// The created object for the response. + /// + /// Returning a from a page handler method is equivalent to returning void. + /// The view associated with the page will be executed. + /// + public virtual PageResult Page() => new PageResult(); + + /// + /// Creates a object that redirects to the specified . + /// + /// The URL to redirect to. + /// The created for the response. + public virtual RedirectResult Redirect(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url); + } + + /// + /// Creates a object with set to true + /// () using the specified . + /// + /// The URL to redirect to. + /// The created for the response. + public virtual RedirectResult RedirectPermanent(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url, permanent: true); + } + + /// + /// Creates a object with set to false + /// and set to true () + /// using the specified . + /// + /// The URL to redirect to. + /// The created for the response. + public virtual RedirectResult RedirectPreserveMethod(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url: url, permanent: false, preserveMethod: true); + } + + /// + /// Creates a object with set to true + /// and set to true () + /// using the specified . + /// + /// The URL to redirect to. + /// The created for the response. + public virtual RedirectResult RedirectPermanentPreserveMethod(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url: url, permanent: true, preserveMethod: true); + } + + /// + /// Redirects () to the specified action using the . + /// + /// The name of the action. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction(string actionName) + => RedirectToAction(actionName, routeValues: null); + + /// + /// Redirects () to the specified action using the + /// and . + /// + /// The name of the action. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction(string actionName, object routeValues) + => RedirectToAction(actionName, controllerName: null, routeValues: routeValues); + + /// + /// Redirects () to the specified action using the + /// and the . + /// + /// The name of the action. + /// The name of the controller. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction(string actionName, string controllerName) + => RedirectToAction(actionName, controllerName, routeValues: null); + + /// + /// Redirects () to the specified action using the specified + /// , , and . + /// + /// The name of the action. + /// The name of the controller. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction( + string actionName, + string controllerName, + object routeValues) + => RedirectToAction(actionName, controllerName, routeValues, fragment: null); + + /// + /// Redirects () to the specified action using the specified + /// , , and . + /// + /// The name of the action. + /// The name of the controller. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction( + string actionName, + string controllerName, + string fragment) + => RedirectToAction(actionName, controllerName, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified action using the specified , + /// , , and . + /// + /// The name of the action. + /// The name of the controller. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction( + string actionName, + string controllerName, + object routeValues, + string fragment) + => new RedirectToActionResult(actionName, controllerName, routeValues, fragment); + + /// + /// Redirects () to the specified action with + /// set to false and + /// set to true, using the specified , , + /// , and . + /// + /// The name of the action. + /// The name of the controller. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPreserveMethod( + string actionName = null, + string controllerName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToActionResult( + actionName: actionName, + controllerName: controllerName, + routeValues: routeValues, + permanent: false, + preserveMethod: true, + fragment: fragment); + } + + /// + /// Redirects () to the specified action with + /// set to true using the specified . + /// + /// The name of the action. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent(string actionName) + { + return RedirectToActionPermanent(actionName, routeValues: null); + } + + /// + /// Redirects () to the specified action with + /// set to true using the specified + /// and . + /// + /// The name of the action. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent(string actionName, object routeValues) + { + return RedirectToActionPermanent(actionName, controllerName: null, routeValues: routeValues); + } + + /// + /// Redirects () to the specified action with + /// set to true using the specified + /// and . + /// + /// The name of the action. + /// The name of the controller. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName) + { + return RedirectToActionPermanent(actionName, controllerName, routeValues: null); + } + + /// + /// Redirects () to the specified action with + /// set to true using the specified , + /// , and . + /// + /// The name of the action. + /// The name of the controller. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent( + string actionName, + string controllerName, + string fragment) + { + return RedirectToActionPermanent(actionName, controllerName, routeValues: null, fragment: fragment); + } + + /// + /// Redirects () to the specified action with + /// set to true using the specified , + /// , and . + /// + /// The name of the action. + /// The name of the controller. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent( + string actionName, + string controllerName, + object routeValues) + { + return RedirectToActionPermanent(actionName, controllerName, routeValues, fragment: null); + } + + /// + /// Redirects () to the specified action with + /// set to true using the specified , + /// , , and . + /// + /// The name of the action. + /// The name of the controller. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent( + string actionName, + string controllerName, + object routeValues, + string fragment) + { + return new RedirectToActionResult( + actionName, + controllerName, + routeValues, + permanent: true, + fragment: fragment); + } + + /// + /// Redirects () to the specified action with + /// set to true and + /// set to true, using the specified , , + /// , and . + /// + /// The name of the action. + /// The name of the controller. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanentPreserveMethod( + string actionName = null, + string controllerName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToActionResult( + actionName: actionName, + controllerName: controllerName, + routeValues: routeValues, + permanent: true, + preserveMethod: true, + fragment: fragment); + } + + /// + /// Redirects () to the specified route using the specified . + /// + /// The name of the route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoute(string routeName) + { + return RedirectToRoute(routeName, routeValues: null); + } + + /// + /// Redirects () to the specified route using the specified . + /// + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoute(object routeValues) + { + return RedirectToRoute(routeName: null, routeValues: routeValues); + } + + /// + /// Redirects () to the specified route using the specified + /// and . + /// + /// The name of the route. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoute(string routeName, object routeValues) + { + return RedirectToRoute(routeName, routeValues, fragment: null); + } + + /// + /// Redirects () to the specified route using the specified + /// and . + /// + /// The name of the route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoute(string routeName, string fragment) + { + return RedirectToRoute(routeName, routeValues: null, fragment: fragment); + } + + /// + /// Redirects () to the specified route using the specified + /// , , and . + /// + /// The name of the route. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoute( + string routeName, + object routeValues, + string fragment) + { + return new RedirectToRouteResult(routeName, routeValues, fragment); + } + + /// + /// Redirects () to the specified route with + /// set to false and + /// set to true, using the specified , , and . + /// + /// The name of the route. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePreserveMethod( + string routeName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToRouteResult( + routeName: routeName, + routeValues: routeValues, + permanent: false, + preserveMethod: true, + fragment: fragment); + } + + /// + /// Redirects () to the specified route with + /// set to true using the specified . + /// + /// The name of the route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName) + { + return RedirectToRoutePermanent(routeName, routeValues: null); + } + + /// + /// Redirects () to the specified route with + /// set to true using the specified . + /// + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanent(object routeValues) + { + return RedirectToRoutePermanent(routeName: null, routeValues: routeValues); + } + + /// + /// Redirects () to the specified route with + /// set to true using the specified + /// and . + /// + /// The name of the route. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, object routeValues) + { + return RedirectToRoutePermanent(routeName, routeValues, fragment: null); + } + + /// + /// Redirects () to the specified route with + /// set to true using the specified + /// and . + /// + /// The name of the route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, string fragment) + { + return RedirectToRoutePermanent(routeName, routeValues: null, fragment: fragment); + } + + /// + /// Redirects () to the specified route with + /// set to true using the specified , + /// , and . + /// + /// The name of the route. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanent( + string routeName, + object routeValues, + string fragment) + => new RedirectToRouteResult(routeName, routeValues, permanent: true, fragment: fragment); + + /// + /// Redirects () to the specified route with + /// set to true and + /// set to true, using the specified , , and . + /// + /// The name of the route. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanentPreserveMethod( + string routeName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToRouteResult( + routeName: routeName, + routeValues: routeValues, + permanent: true, + preserveMethod: true, + fragment: fragment); + } + + /// + /// Redirects () to the current page. + /// + /// The . + public virtual RedirectToPageResult RedirectToPage() + => RedirectToPage(pageName: null); + + /// + /// Redirects () to the current page with the specified . + /// + /// The parameters for a route. + /// The . + public virtual RedirectToPageResult RedirectToPage(object routeValues) + => RedirectToPage(pageName: null, routeValues: routeValues); + + /// + /// Redirects () to the specified . + /// + /// The name of the page. + /// The . + public virtual RedirectToPageResult RedirectToPage(string pageName) + => RedirectToPage(pageName, routeValues: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The . + public virtual RedirectToPageResult RedirectToPage(string pageName, string pageHandler) + => RedirectToPage(pageName, pageHandler, routeValues: null, fragment: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The parameters for a route. + /// The . + public virtual RedirectToPageResult RedirectToPage(string pageName, object routeValues) + => RedirectToPage(pageName, pageHandler: null, routeValues: routeValues, fragment: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The fragment to add to the URL. + /// The . + public virtual RedirectToPageResult RedirectToPage(string pageName, string pageHandler, string fragment) + => RedirectToPage(pageName, pageHandler, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified + /// using the specified and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The . + public virtual RedirectToPageResult RedirectToPage(string pageName, string pageHandler, object routeValues, string fragment) + => new RedirectToPageResult(pageName, pageHandler, routeValues, fragment); + + /// + /// Redirects () to the specified . + /// + /// The name of the page. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName) + => RedirectToPagePermanent(pageName, pageHandler: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The parameters for a route. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, object routeValues) + => RedirectToPagePermanent(pageName, pageHandler: null, routeValues: routeValues, fragment: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, string pageHandler) + => RedirectToPagePermanent(pageName, pageHandler, routeValues: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for a route. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, string pageHandler, object routeValues) + => RedirectToPagePermanent(pageName, pageHandler, routeValues, fragment: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The fragment to add to the URL. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, string pageHandler, string fragment) + => RedirectToPagePermanent(pageName, pageHandler, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified + /// using the specified and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, string pageHandler, object routeValues, string fragment) + => new RedirectToPageResult(pageName, pageHandler, routeValues, permanent: true, fragment: fragment); + + /// + /// Redirects () to the specified page with + /// set to false and + /// set to true, using the specified , , and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToPageResult RedirectToPagePreserveMethod( + string pageName = null, + string pageHandler = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToPageResult( + pageName: pageName, + pageHandler: pageHandler, + routeValues: routeValues, + permanent: false, + preserveMethod: true, + fragment: fragment); + } + + /// + /// Redirects () to the specified route with + /// set to true and + /// set to true, using the specified , , and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToPageResult RedirectToPagePermanentPreserveMethod( + string pageName = null, + string pageHandler = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToPageResult( + pageName: pageName, + pageHandler: pageHandler, + routeValues: routeValues, + permanent: true, + preserveMethod: true, + fragment: fragment); + } + + /// + /// Creates a with the specified authentication scheme. + /// + /// The containing the user claims. + /// The authentication scheme to use for the sign-in operation. + /// The created for the response. + public virtual SignInResult SignIn(ClaimsPrincipal principal, string authenticationScheme) + => new SignInResult(authenticationScheme, principal); + + /// + /// Creates a with the specified authentication scheme and + /// . + /// + /// The containing the user claims. + /// used to perform the sign-in operation. + /// The authentication scheme to use for the sign-in operation. + /// The created for the response. + public virtual SignInResult SignIn( + ClaimsPrincipal principal, + AuthenticationProperties properties, + string authenticationScheme) + => new SignInResult(authenticationScheme, principal, properties); + + /// + /// Creates a with the specified authentication schemes. + /// + /// The authentication schemes to use for the sign-out operation. + /// The created for the response. + public virtual SignOutResult SignOut(params string[] authenticationSchemes) + => new SignOutResult(authenticationSchemes); + + /// + /// Creates a with the specified authentication schemes and + /// . + /// + /// used to perform the sign-out operation. + /// The authentication scheme to use for the sign-out operation. + /// The created for the response. + public virtual SignOutResult SignOut(AuthenticationProperties properties, params string[] authenticationSchemes) + => new SignOutResult(authenticationSchemes, properties); + + /// + /// Creates a object by specifying a . + /// + /// The status code to set on the response. + /// The created object for the response. + public virtual StatusCodeResult StatusCode(int statusCode) + => new StatusCodeResult(statusCode); + + /// + /// Creates a object by specifying a and + /// + /// The status code to set on the response. + /// The value to set on the . + /// The created object for the response. + public virtual ObjectResult StatusCode(int statusCode, object value) + => new ObjectResult(value) { StatusCode = statusCode }; + + /// + /// Creates an that produces an response. + /// + /// The created for the response. + public virtual UnauthorizedResult Unauthorized() + => new UnauthorizedResult(); + + /// + /// Updates the specified instance using values from the 's current + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// A that on completion returns true if the update is successful. + public virtual Task TryUpdateModelAsync( + TModel model) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + return TryUpdateModelAsync(model, prefix: string.Empty); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// A that on completion returns true if the update is successful. + public virtual async Task TryUpdateModelAsync( + TModel model, + string prefix) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories); + return await TryUpdateModelAsync(model, prefix, valueProvider); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// A that on completion returns true if the update is successful. + public virtual Task TryUpdateModelAsync( + TModel model, + string prefix, + IValueProvider valueProvider) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (prefix == null) + { + throw new ArgumentNullException(nameof(prefix)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + prefix, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// (s) which represent top-level properties + /// which need to be included for the current model. + /// A that on completion returns true if the update is successful. + public async Task TryUpdateModelAsync( + TModel model, + string prefix, + params Expression>[] includeExpressions) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (includeExpressions == null) + { + throw new ArgumentNullException(nameof(includeExpressions)); + } + + var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories); + return await ModelBindingHelper.TryUpdateModelAsync( + model, + prefix, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + includeExpressions); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + public async Task TryUpdateModelAsync( + TModel model, + string prefix, + Func propertyFilter) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (propertyFilter == null) + { + throw new ArgumentNullException(nameof(propertyFilter)); + } + + var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories); + return await ModelBindingHelper.TryUpdateModelAsync( + model, + prefix, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + propertyFilter); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// (s) which represent top-level properties + /// which need to be included for the current model. + /// A that on completion returns true if the update is successful. + public Task TryUpdateModelAsync( + TModel model, + string prefix, + IValueProvider valueProvider, + params Expression>[] includeExpressions) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (includeExpressions == null) + { + throw new ArgumentNullException(nameof(includeExpressions)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + prefix, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + includeExpressions); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + public Task TryUpdateModelAsync( + TModel model, + string prefix, + IValueProvider valueProvider, + Func propertyFilter) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (propertyFilter == null) + { + throw new ArgumentNullException(nameof(propertyFilter)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + prefix, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + propertyFilter); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The prefix to use when looking up values in the current . + /// + /// A that on completion returns true if the update is successful. + public virtual async Task TryUpdateModelAsync( + object model, + Type modelType, + string prefix) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories); + return await ModelBindingHelper.TryUpdateModelAsync( + model, + modelType, + prefix, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The prefix to use when looking up values in the . + /// + /// The used for looking up values. + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + public Task TryUpdateModelAsync( + object model, + Type modelType, + string prefix, + IValueProvider valueProvider, + Func propertyFilter) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (propertyFilter == null) + { + throw new ArgumentNullException(nameof(propertyFilter)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + modelType, + prefix, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + propertyFilter); + } + + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// true if the is valid; false otherwise. + public virtual bool TryValidateModel( + object model) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + return TryValidateModel(model, prefix: null); + } + + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// The key to use when looking up information in . + /// + /// true if the is valid;false otherwise. + public virtual bool TryValidateModel( + object model, + string prefix) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + ObjectValidator.Validate( + PageContext, + validationState: null, + prefix: prefix ?? string.Empty, + model: model); + return ModelState.IsValid; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs new file mode 100644 index 0000000000..cfede5d167 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.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; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + /// + /// The context associated with the current request for a Razor page. + /// + public class PageContext : ActionContext + { + private CompiledPageActionDescriptor _actionDescriptor; + private IList _valueProviderFactories; + private ViewDataDictionary _viewData; + private IList> _viewStartFactories; + + /// + /// Creates an empty . + /// + /// + /// The default constructor is provided for unit test purposes only. + /// + public PageContext() + { + } + + /// + /// Initializes a new instance of . + /// + /// The . + public PageContext(ActionContext actionContext) + : base(actionContext) + { + } + + /// + /// Gets or sets the . + /// + public virtual new CompiledPageActionDescriptor ActionDescriptor + { + get => _actionDescriptor; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _actionDescriptor = value; + base.ActionDescriptor = value; + } + } + + /// + /// Gets or sets the list of instances for the current request. + /// + public virtual IList ValueProviderFactories + { + get + { + if (_valueProviderFactories == null) + { + _valueProviderFactories = new List(); + } + + return _valueProviderFactories; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _valueProviderFactories = value; + } + } + + /// + /// Gets or sets . + /// + public virtual ViewDataDictionary ViewData + { + get => _viewData; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _viewData = value; + } + } + + /// + /// Gets or sets the applicable _ViewStart instances. + /// + public virtual IList> ViewStartFactories + { + get => _viewStartFactories; + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _viewStartFactories = value; + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContextAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContextAttribute.cs new file mode 100644 index 0000000000..d92ec99c26 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContextAttribute.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 System; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + /// + /// Specifies that a Razor Page model property should be set with the current when creating + /// the model instance. The property must have a public set method. + /// + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public class PageContextAttribute : Attribute + { + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs new file mode 100644 index 0000000000..ce5a76af27 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs @@ -0,0 +1,1729 @@ +// Copyright (c) .NET 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.Expressions; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Net.Http.Headers; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + [PageModel] + public abstract class PageModel : IAsyncPageFilter, IPageFilter + { + private IModelMetadataProvider _metadataProvider; + private IModelBinderFactory _modelBinderFactory; + private IObjectModelValidator _objectValidator; + private ITempDataDictionary _tempData; + private IUrlHelper _urlHelper; + private PageContext _pageContext; + + /// + /// Gets the . + /// + [PageContext] + public PageContext PageContext + { + get + { + if (_pageContext == null) + { + _pageContext = new PageContext(); + } + + return _pageContext; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _pageContext = value; + } + } + + /// + /// Gets the . + /// + public HttpContext HttpContext => PageContext.HttpContext; + + /// + /// Gets the . + /// + public HttpRequest Request => HttpContext?.Request; + + /// + /// Gets the . + /// + public HttpResponse Response => HttpContext?.Response; + + /// + /// Gets the for the executing action. + /// + public RouteData RouteData => PageContext.RouteData; + + /// + /// Gets the . + /// + public ModelStateDictionary ModelState => PageContext.ModelState; + + /// + /// Gets the for user associated with the executing action. + /// + public ClaimsPrincipal User => HttpContext?.User; + + /// + /// Gets or sets used by . + /// + public ITempDataDictionary TempData + { + get + { + if (_tempData == null) + { + var factory = HttpContext?.RequestServices?.GetRequiredService(); + _tempData = factory?.GetTempData(HttpContext); + } + + return _tempData; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _tempData = value; + } + } + + /// + /// Gets or sets the . + /// + public IUrlHelper Url + { + get + { + if (_urlHelper == null) + { + var factory = HttpContext?.RequestServices?.GetRequiredService(); + _urlHelper = factory?.GetUrlHelper(PageContext); + } + + return _urlHelper; + } + set + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + _urlHelper = value; + } + } + + /// + /// Gets or sets used by . + /// + public ViewDataDictionary ViewData => PageContext?.ViewData; + + private IObjectModelValidator ObjectValidator + { + get + { + if (_objectValidator == null) + { + _objectValidator = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _objectValidator; + } + } + + private IModelMetadataProvider MetadataProvider + { + get + { + if (_metadataProvider == null) + { + _metadataProvider = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _metadataProvider; + } + } + + private IModelBinderFactory ModelBinderFactory + { + get + { + if (_modelBinderFactory == null) + { + _modelBinderFactory = HttpContext?.RequestServices?.GetRequiredService(); + } + + return _modelBinderFactory; + } + } + + /// + /// Updates the specified instance using values from the 's current + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// A that on completion returns true if the update is successful. + protected internal Task TryUpdateModelAsync(TModel model) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + return TryUpdateModelAsync(model, name: string.Empty); + } + + /// + /// Updates the specified instance using values from the 's current + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The model name. + /// A that on completion returns true if the update is successful. + protected internal async Task TryUpdateModelAsync(TModel model, string name) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories); + return await TryUpdateModelAsync(model, name, valueProvider); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The name to use when looking up values in the . + /// + /// The used for looking up values. + /// A that on completion returns true if the update is successful. + protected internal Task TryUpdateModelAsync( + TModel model, + string name, + IValueProvider valueProvider) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (name == null) + { + throw new ArgumentNullException(nameof(name)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + name, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The name to use when looking up values in the current . + /// + /// (s) which represent top-level properties + /// which need to be included for the current model. + /// A that on completion returns true if the update is successful. + protected internal async Task TryUpdateModelAsync( + TModel model, + string name, + params Expression>[] includeExpressions) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (includeExpressions == null) + { + throw new ArgumentNullException(nameof(includeExpressions)); + } + + var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories); + return await ModelBindingHelper.TryUpdateModelAsync( + model, + name, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + includeExpressions); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The type of the model object. + /// The model instance to update. + /// The name to use when looking up values in the current . + /// + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + protected internal async Task TryUpdateModelAsync( + TModel model, + string name, + Func propertyFilter) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (propertyFilter == null) + { + throw new ArgumentNullException(nameof(propertyFilter)); + } + + var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories); + return await ModelBindingHelper.TryUpdateModelAsync( + model, + name, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + propertyFilter); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The name to use when looking up values in the . + /// + /// The used for looking up values. + /// (s) which represent top-level properties + /// which need to be included for the current model. + /// A that on completion returns true if the update is successful. + protected internal Task TryUpdateModelAsync( + TModel model, + string name, + IValueProvider valueProvider, + params Expression>[] includeExpressions) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (includeExpressions == null) + { + throw new ArgumentNullException(nameof(includeExpressions)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + name, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + includeExpressions); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The type of the model object. + /// The model instance to update. + /// The name to use when looking up values in the . + /// + /// The used for looking up values. + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + protected internal Task TryUpdateModelAsync( + TModel model, + string name, + IValueProvider valueProvider, + Func propertyFilter) + where TModel : class + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (propertyFilter == null) + { + throw new ArgumentNullException(nameof(propertyFilter)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + name, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + propertyFilter); + } + + /// + /// Updates the specified instance using values from the 's current + /// and a . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The name to use when looking up values in the current . + /// + /// A that on completion returns true if the update is successful. + protected internal async Task TryUpdateModelAsync( + object model, + Type modelType, + string name) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + var valueProvider = await CompositeValueProvider.CreateAsync(PageContext, PageContext.ValueProviderFactories); + return await ModelBindingHelper.TryUpdateModelAsync( + model, + modelType, + name, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator); + } + + /// + /// Updates the specified instance using the and a + /// . + /// + /// The model instance to update. + /// The type of model instance to update. + /// The name to use when looking up values in the . + /// + /// The used for looking up values. + /// A predicate which can be used to filter properties at runtime. + /// A that on completion returns true if the update is successful. + protected internal Task TryUpdateModelAsync( + object model, + Type modelType, + string name, + IValueProvider valueProvider, + Func propertyFilter) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + if (valueProvider == null) + { + throw new ArgumentNullException(nameof(valueProvider)); + } + + if (propertyFilter == null) + { + throw new ArgumentNullException(nameof(propertyFilter)); + } + + return ModelBindingHelper.TryUpdateModelAsync( + model, + modelType, + name, + PageContext, + MetadataProvider, + ModelBinderFactory, + valueProvider, + ObjectValidator, + propertyFilter); + } + + /// + /// Creates a that produces a response. + /// + /// The created for the response. + [NonAction] + public virtual BadRequestResult BadRequest() + => new BadRequestResult(); + + /// + /// Creates a that produces a response. + /// + /// An error object to be returned to the client. + /// The created for the response. + [NonAction] + public virtual BadRequestObjectResult BadRequest(object error) + => new BadRequestObjectResult(error); + + /// + /// Creates a that produces a response. + /// + /// The containing errors to be returned to the client. + /// The created for the response. + [NonAction] + public virtual BadRequestObjectResult BadRequest(ModelStateDictionary modelState) + { + if (modelState == null) + { + throw new ArgumentNullException(nameof(modelState)); + } + + return new BadRequestObjectResult(modelState); + } + + /// + /// Creates a . + /// + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + public virtual ChallengeResult Challenge() + => new ChallengeResult(); + + /// + /// Creates a with the specified authentication schemes. + /// + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + public virtual ChallengeResult Challenge(params string[] authenticationSchemes) + => new ChallengeResult(authenticationSchemes); + + /// + /// Creates a with the specified . + /// + /// used to perform the authentication + /// challenge. + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + public virtual ChallengeResult Challenge(AuthenticationProperties properties) + => new ChallengeResult(properties); + + /// + /// Creates a with the specified authentication schemes and + /// . + /// + /// used to perform the authentication + /// challenge. + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// The behavior of this method depends on the in use. + /// and + /// are among likely status results. + /// + public virtual ChallengeResult Challenge( + AuthenticationProperties properties, + params string[] authenticationSchemes) + => new ChallengeResult(authenticationSchemes, properties); + + /// + /// Creates a object with by specifying a + /// string. + /// + /// The content to write to the response. + /// The created object for the response. + public virtual ContentResult Content(string content) + => Content(content, (MediaTypeHeaderValue)null); + + /// + /// Creates a object with by specifying a + /// string and a content type. + /// + /// The content to write to the response. + /// The content type (MIME type). + /// The created object for the response. + public virtual ContentResult Content(string content, string contentType) + => Content(content, MediaTypeHeaderValue.Parse(contentType)); + + /// + /// Creates a object with by specifying a + /// string, a , and . + /// + /// The content to write to the response. + /// The content type (MIME type). + /// The content encoding. + /// The created object for the response. + /// + /// If encoding is provided by both the 'charset' and the parameters, then + /// the parameter is chosen as the final encoding. + /// + public virtual ContentResult Content(string content, string contentType, Encoding contentEncoding) + { + var mediaTypeHeaderValue = MediaTypeHeaderValue.Parse(contentType); + mediaTypeHeaderValue.Encoding = contentEncoding ?? mediaTypeHeaderValue.Encoding; + return Content(content, mediaTypeHeaderValue); + } + + /// + /// Creates a object with by specifying a + /// string and a . + /// + /// The content to write to the response. + /// The content type (MIME type). + /// The created object for the response. + public virtual ContentResult Content(string content, MediaTypeHeaderValue contentType) + { + return new ContentResult + { + Content = content, + ContentType = contentType?.ToString() + }; + } + + /// + /// Creates a ( by default). + /// + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + public virtual ForbidResult Forbid() + => new ForbidResult(); + + /// + /// Creates a ( by default) with the + /// specified authentication schemes. + /// + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + public virtual ForbidResult Forbid(params string[] authenticationSchemes) + => new ForbidResult(authenticationSchemes); + + /// + /// Creates a ( by default) with the + /// specified . + /// + /// used to perform the authentication + /// challenge. + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + public virtual ForbidResult Forbid(AuthenticationProperties properties) + => new ForbidResult(properties); + + /// + /// Creates a ( by default) with the + /// specified authentication schemes and . + /// + /// used to perform the authentication + /// challenge. + /// The authentication schemes to challenge. + /// The created for the response. + /// + /// Some authentication schemes, such as cookies, will convert to + /// a redirect to show a login page. + /// + public virtual ForbidResult Forbid(AuthenticationProperties properties, params string[] authenticationSchemes) + => new ForbidResult(authenticationSchemes, properties); + + /// + /// Returns a file with the specified as content + /// () and the specified as the Content-Type. + /// + /// The file contents. + /// The Content-Type of the file. + /// The created for the response. + public virtual FileContentResult File(byte[] fileContents, string contentType) + => File(fileContents, contentType, fileDownloadName: null); + + /// + /// Returns a file with the specified as content (), the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// + /// The file contents. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + public virtual FileContentResult File(byte[] fileContents, string contentType, string fileDownloadName) + => new FileContentResult(fileContents, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Returns a file in the specified () + /// with the specified as the Content-Type. + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// The created for the response. + public virtual FileStreamResult File(Stream fileStream, string contentType) + => File(fileStream, contentType, fileDownloadName: null); + + /// + /// Returns a file in the specified () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// + /// The with the contents of the file. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + public virtual FileStreamResult File(Stream fileStream, string contentType, string fileDownloadName) + => new FileStreamResult(fileStream, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type. + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// The created for the response. + public virtual VirtualFileResult File(string virtualPath, string contentType) + => File(virtualPath, contentType, fileDownloadName: null); + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// + /// The virtual path of the file to be returned. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + public virtual VirtualFileResult File(string virtualPath, string contentType, string fileDownloadName) + => new VirtualFileResult(virtualPath, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Creates a object that redirects + /// () to the specified local . + /// + /// The local URL to redirect to. + /// The created for the response. + public virtual LocalRedirectResult LocalRedirect(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl); + } + + /// + /// Creates a object with set to + /// true () using the specified . + /// + /// The local URL to redirect to. + /// The created for the response. + public virtual LocalRedirectResult LocalRedirectPermanent(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl, permanent: true); + } + + /// + /// Creates a object with set to + /// false and set to true + /// () using the specified . + /// + /// The local URL to redirect to. + /// The created for the response. + public virtual LocalRedirectResult LocalRedirectPreserveMethod(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl: localUrl, permanent: false, preserveMethod: true); + } + + /// + /// Creates a object with set to + /// true and set to true + /// () using the specified . + /// + /// The local URL to redirect to. + /// The created for the response. + public virtual LocalRedirectResult LocalRedirectPermanentPreserveMethod(string localUrl) + { + if (string.IsNullOrEmpty(localUrl)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(localUrl)); + } + + return new LocalRedirectResult(localUrl: localUrl, permanent: true, preserveMethod: true); + } + + /// + /// Creates an that produces a response. + /// + /// The created for the response. + public virtual NotFoundResult NotFound() + => new NotFoundResult(); + + /// + /// Creates an that produces a response. + /// + /// The created for the response. + public virtual NotFoundObjectResult NotFound(object value) + => new NotFoundObjectResult(value); + + /// + /// Creates a object that renders the page. + /// + /// The . + public virtual PageResult Page() => new PageResult(); + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type. + /// + /// The physical path of the file to be returned. + /// The Content-Type of the file. + /// The created for the response. + public virtual PhysicalFileResult PhysicalFile(string physicalPath, string contentType) + => PhysicalFile(physicalPath, contentType, fileDownloadName: null); + + /// + /// Returns the file specified by () with the + /// specified as the Content-Type and the + /// specified as the suggested file name. + /// + /// The physical path of the file to be returned. + /// The Content-Type of the file. + /// The suggested file name. + /// The created for the response. + public virtual PhysicalFileResult PhysicalFile( + string physicalPath, + string contentType, + string fileDownloadName) + => new PhysicalFileResult(physicalPath, contentType) { FileDownloadName = fileDownloadName }; + + /// + /// Creates a object that redirects () + /// to the specified . + /// + /// The URL to redirect to. + /// The created for the response. + protected internal RedirectResult Redirect(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url); + } + + /// + /// Creates a object with set to true + /// () using the specified . + /// + /// The URL to redirect to. + /// The created for the response. + public virtual RedirectResult RedirectPermanent(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url, permanent: true); + } + + /// + /// Creates a object with set to false + /// and set to true () + /// using the specified . + /// + /// The URL to redirect to. + /// The created for the response. + public virtual RedirectResult RedirectPreserveMethod(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url: url, permanent: false, preserveMethod: true); + } + + /// + /// Creates a object with set to true + /// and set to true () + /// using the specified . + /// + /// The URL to redirect to. + /// The created for the response. + public virtual RedirectResult RedirectPermanentPreserveMethod(string url) + { + if (string.IsNullOrEmpty(url)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(url)); + } + + return new RedirectResult(url: url, permanent: true, preserveMethod: true); + } + + /// + /// Redirects () to the specified action using the . + /// + /// The name of the action. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction(string actionName) + => RedirectToAction(actionName, routeValues: null); + + /// + /// Redirects () to the specified action using the + /// and . + /// + /// The name of the action. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction(string actionName, object routeValues) + => RedirectToAction(actionName, controllerName: null, routeValues: routeValues); + + /// + /// Redirects () to the specified action using the + /// and the . + /// + /// The name of the action. + /// The name of the pageModel. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction(string actionName, string controllerName) + => RedirectToAction(actionName, controllerName, routeValues: null); + + /// + /// Redirects () to the specified action using the specified + /// , , and . + /// + /// The name of the action. + /// The name of the pageModel. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction( + string actionName, + string controllerName, + object routeValues) + => RedirectToAction(actionName, controllerName, routeValues, fragment: null); + + /// + /// Redirects () to the specified action using the specified + /// , , and . + /// + /// The name of the action. + /// The name of the pageModel. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction( + string actionName, + string controllerName, + string fragment) + => RedirectToAction(actionName, controllerName, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified action using the specified , + /// , , and . + /// + /// The name of the action. + /// The name of the pageModel. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToAction( + string actionName, + string controllerName, + object routeValues, + string fragment) + { + return new RedirectToActionResult(actionName, controllerName, routeValues, fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified action with + /// set to false and + /// set to true, using the specified , , + /// , and . + /// + /// The name of the action. + /// The name of the pageModel. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPreserveMethod( + string actionName = null, + string controllerName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToActionResult( + actionName: actionName, + controllerName: controllerName, + routeValues: routeValues, + permanent: false, + preserveMethod: true, + fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified action with + /// set to true using the specified . + /// + /// The name of the action. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent(string actionName) + => RedirectToActionPermanent(actionName, routeValues: null); + + /// + /// Redirects () to the specified action with + /// set to true using the specified + /// and . + /// + /// The name of the action. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent(string actionName, object routeValues) + => RedirectToActionPermanent(actionName, controllerName: null, routeValues: routeValues); + + /// + /// Redirects () to the specified action with + /// set to true using the specified + /// and . + /// + /// The name of the action. + /// The name of the pageModel. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName) + => RedirectToActionPermanent(actionName, controllerName, routeValues: null); + + /// + /// Redirects () to the specified action with + /// set to true using the specified , + /// , and . + /// + /// The name of the action. + /// The name of the pageModel. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent( + string actionName, + string controllerName, + string fragment) + => RedirectToActionPermanent(actionName, controllerName, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified action with + /// set to true using the specified , + /// , and . + /// + /// The name of the action. + /// The name of the pageModel. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent( + string actionName, + string controllerName, + object routeValues) + => RedirectToActionPermanent(actionName, controllerName, routeValues, fragment: null); + + /// + /// Redirects () to the specified action with + /// set to true using the specified , + /// , , and . + /// + /// The name of the action. + /// The name of the pageModel. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanent( + string actionName, + string controllerName, + object routeValues, + string fragment) + { + return new RedirectToActionResult( + actionName, + controllerName, + routeValues, + permanent: true, + fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified action with + /// set to true and + /// set to true, using the specified , , + /// , and . + /// + /// The name of the action. + /// The name of the pageModel. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToActionResult RedirectToActionPermanentPreserveMethod( + string actionName = null, + string controllerName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToActionResult( + actionName: actionName, + controllerName: controllerName, + routeValues: routeValues, + permanent: true, + preserveMethod: true, + fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified route using the specified . + /// + /// The name of the route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoute(string routeName) + => RedirectToRoute(routeName, routeValues: null); + + /// + /// Redirects () to the specified route using the specified . + /// + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoute(object routeValues) + => RedirectToRoute(routeName: null, routeValues: routeValues); + + /// + /// Redirects () to the specified route using the specified + /// and . + /// + /// The name of the route. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoute(string routeName, object routeValues) + => RedirectToRoute(routeName, routeValues, fragment: null); + + /// + /// Redirects () to the specified route using the specified + /// and . + /// + /// The name of the route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoute(string routeName, string fragment) + => RedirectToRoute(routeName, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified route using the specified + /// , , and . + /// + /// The name of the route. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoute( + string routeName, + object routeValues, + string fragment) + { + return new RedirectToRouteResult(routeName, routeValues, fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified route with + /// set to false and + /// set to true, using the specified , , and . + /// + /// The name of the route. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePreserveMethod( + string routeName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToRouteResult( + routeName: routeName, + routeValues: routeValues, + permanent: false, + preserveMethod: true, + fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified route with + /// set to true using the specified . + /// + /// The name of the route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName) + => RedirectToRoutePermanent(routeName, routeValues: null); + + /// + /// Redirects () to the specified route with + /// set to true using the specified . + /// + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanent(object routeValues) + => RedirectToRoutePermanent(routeName: null, routeValues: routeValues); + + /// + /// Redirects () to the specified route with + /// set to true using the specified + /// and . + /// + /// The name of the route. + /// The parameters for a route. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, object routeValues) + => RedirectToRoutePermanent(routeName, routeValues, fragment: null); + + /// + /// Redirects () to the specified route with + /// set to true using the specified + /// and . + /// + /// The name of the route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, string fragment) + => RedirectToRoutePermanent(routeName, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified route with + /// set to true using the specified , + /// , and . + /// + /// The name of the route. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanent( + string routeName, + object routeValues, + string fragment) + { + return new RedirectToRouteResult(routeName, routeValues, permanent: true, fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the specified route with + /// set to true and + /// set to true, using the specified , , and . + /// + /// The name of the route. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToRouteResult RedirectToRoutePermanentPreserveMethod( + string routeName = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToRouteResult( + routeName: routeName, + routeValues: routeValues, + permanent: true, + preserveMethod: true, + fragment: fragment) + { + UrlHelper = Url, + }; + } + + /// + /// Redirects () to the current page. + /// + /// The . + public virtual RedirectToPageResult RedirectToPage() + => RedirectToPage(pageName: null); + + /// + /// Redirects () to the current page with the specified . + /// + /// The parameters for a route. + /// The . + public virtual RedirectToPageResult RedirectToPage(object routeValues) + => RedirectToPage(pageName: null, routeValues: routeValues); + + /// + /// Redirects () to the specified . + /// + /// The name of the page. + /// The . + public virtual RedirectToPageResult RedirectToPage(string pageName) + => RedirectToPage(pageName, routeValues: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The . + public virtual RedirectToPageResult RedirectToPage(string pageName, string pageHandler) + => RedirectToPage(pageName, pageHandler, routeValues: null); + + /// + /// Redirects () to the specified + /// using the specified and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for a route. + /// The . + public virtual RedirectToPageResult RedirectToPage(string pageName, string pageHandler, object routeValues) + => RedirectToPage(pageName, pageHandler, routeValues, fragment: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The parameters for a route. + /// The . + public virtual RedirectToPageResult RedirectToPage(string pageName, object routeValues) + => RedirectToPage(pageName, pageHandler: null, routeValues: routeValues, fragment: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The fragment to add to the URL. + /// The . + public virtual RedirectToPageResult RedirectToPage(string pageName, string pageHandler, string fragment) + => RedirectToPage(pageName, pageHandler, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified + /// using the specified and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The . + public virtual RedirectToPageResult RedirectToPage(string pageName, string pageHandler, object routeValues, string fragment) + => new RedirectToPageResult(pageName, pageHandler, routeValues, fragment); + + /// + /// Redirects () to the specified . + /// + /// The name of the page. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName) + => RedirectToPagePermanent(pageName, pageHandler: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The parameters for a route. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, object routeValues) + => RedirectToPagePermanent(pageName, pageHandler: null, routeValues: routeValues, fragment: null); + + /// + /// Redirects () to the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, string pageHandler) + => RedirectToPagePermanent(pageName, pageHandler, routeValues: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for a route. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, string pageHandler, object routeValues) + => RedirectToPagePermanent(pageName, pageHandler, routeValues, fragment: null); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The fragment to add to the URL. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, string pageHandler, string fragment) + => RedirectToPagePermanent(pageName, pageHandler, routeValues: null, fragment: fragment); + + /// + /// Redirects () to the specified + /// using the specified . + /// + /// The name of the page. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, object routeValues, string fragment) + => RedirectToPagePermanent(pageName, pageHandler: null, routeValues: routeValues, fragment: fragment); + + /// + /// Redirects () to the specified + /// using the specified and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The parameters for a route. + /// The fragment to add to the URL. + /// The with set. + public virtual RedirectToPageResult RedirectToPagePermanent(string pageName, string pageHandler, object routeValues, string fragment) + => new RedirectToPageResult(pageName, pageHandler, routeValues, permanent: true, fragment: fragment); + + /// + /// Redirects () to the specified page with + /// set to false and + /// set to true, using the specified , , and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToPageResult RedirectToPagePreserveMethod( + string pageName = null, + string pageHandler = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToPageResult( + pageName: pageName, + pageHandler: pageHandler, + routeValues: routeValues, + permanent: false, + preserveMethod: true, + fragment: fragment); + } + + /// + /// Redirects () to the specified route with + /// set to true and + /// set to true, using the specified , , and . + /// + /// The name of the page. + /// The page handler to redirect to. + /// The route data to use for generating the URL. + /// The fragment to add to the URL. + /// The created for the response. + public virtual RedirectToPageResult RedirectToPagePermanentPreserveMethod( + string pageName = null, + string pageHandler = null, + object routeValues = null, + string fragment = null) + { + return new RedirectToPageResult( + pageName: pageName, + pageHandler: pageHandler, + routeValues: routeValues, + permanent: true, + preserveMethod: true, + fragment: fragment); + } + + /// + /// Creates a with the specified authentication scheme. + /// + /// The containing the user claims. + /// The authentication scheme to use for the sign-in operation. + /// The created for the response. + public virtual SignInResult SignIn(ClaimsPrincipal principal, string authenticationScheme) + => new SignInResult(authenticationScheme, principal); + + /// + /// Creates a with the specified authentication scheme and + /// . + /// + /// The containing the user claims. + /// used to perform the sign-in operation. + /// The authentication scheme to use for the sign-in operation. + /// The created for the response. + public virtual SignInResult SignIn( + ClaimsPrincipal principal, + AuthenticationProperties properties, + string authenticationScheme) + => new SignInResult(authenticationScheme, principal, properties); + + /// + /// Creates a with the specified authentication schemes. + /// + /// The authentication schemes to use for the sign-out operation. + /// The created for the response. + public virtual SignOutResult SignOut(params string[] authenticationSchemes) + => new SignOutResult(authenticationSchemes); + + /// + /// Creates a with the specified authentication schemes and + /// . + /// + /// used to perform the sign-out operation. + /// The authentication scheme to use for the sign-out operation. + /// The created for the response. + public virtual SignOutResult SignOut(AuthenticationProperties properties, params string[] authenticationSchemes) + => new SignOutResult(authenticationSchemes, properties); + + /// + /// Creates a object by specifying a . + /// + /// The status code to set on the response. + /// The created object for the response. + public virtual StatusCodeResult StatusCode(int statusCode) + => new StatusCodeResult(statusCode); + + /// + /// Creates a object by specifying a and + /// + /// The status code to set on the response. + /// The value to set on the . + /// The created object for the response. + public virtual ObjectResult StatusCode(int statusCode, object value) + { + return new ObjectResult(value) + { + StatusCode = statusCode + }; + } + + /// + /// Creates an that produces an response. + /// + /// The created for the response. + public virtual UnauthorizedResult Unauthorized() + => new UnauthorizedResult(); + + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// true if the is valid; false otherwise. + public virtual bool TryValidateModel( + object model) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + return TryValidateModel(model, name: null); + } + + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// The key to use when looking up information in . + /// + /// true if the is valid;false otherwise. + public virtual bool TryValidateModel( + object model, + string name) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + + ObjectValidator.Validate( + PageContext, + validationState: null, + prefix: name ?? string.Empty, + model: model); + return ModelState.IsValid; + } + +#region IAsyncPageFilter \ IPageFilter + /// + /// Called after a handler method has been selected, but before model binding occurs. + /// + /// The . + public virtual void OnPageHandlerSelected(PageHandlerSelectedContext context) + { + } + + /// + /// Called before the handler method executes, after model binding is complete. + /// + /// The . + public virtual void OnPageHandlerExecuting(PageHandlerExecutingContext context) + { + } + + /// + /// Called after the handler method executes, before the action result executes. + /// + /// The . + public virtual void OnPageHandlerExecuted(PageHandlerExecutedContext context) + { + } + + /// + /// Called asynchronously after the handler method has been selected, but before model binding occurs. + /// + /// The . + /// A that on completion indicates the filter has executed. + public virtual Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + OnPageHandlerSelected(context); + return Task.CompletedTask; + } + + /// + /// Called asynchronously before the handler method is invoked, after model binding is complete. + /// + /// The . + /// + /// The . Invoked to execute the next page filter or the handler method itself. + /// + /// A that on completion indicates the filter has executed. + public virtual async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (next == null) + { + throw new ArgumentNullException(nameof(next)); + } + + OnPageHandlerExecuting(context); + if (context.Result == null) + { + OnPageHandlerExecuted(await next()); + } + } +#endregion + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageResult.cs new file mode 100644 index 0000000000..1f533b93fc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageResult.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 System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + /// + /// An that renders a Razor Page. + /// + public class PageResult : ActionResult + { + /// + /// Gets or sets the Content-Type header for the response. + /// + public string ContentType { get; set; } + + /// + /// Gets the page model. + /// + public object Model => ViewData?.Model; + + /// + /// Gets or sets the to be executed. + /// + public PageBase Page { get; set; } + + /// + /// Gets or sets the for the page to be executed. + /// + public ViewDataDictionary ViewData { get; set; } + + /// + /// Gets or sets the HTTP status code. + /// + public int? StatusCode { get; set; } + + /// + public override Task ExecuteResultAsync(ActionContext context) + { + if (!(context is PageContext pageContext)) + { + throw new ArgumentException(Resources.FormatPageViewResult_ContextIsInvalid( + nameof(context), + nameof(Page), + nameof(PageResult))); + } + + var executor = context.HttpContext.RequestServices.GetRequiredService(); + return executor.ExecuteAsync(pageContext, this); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..458502d802 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/AssemblyInfo.cs @@ -0,0 +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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..eec543945b --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs @@ -0,0 +1,198 @@ +// +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Mvc.RazorPages.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// The '{0}' property of '{1}' must not be null. + /// + internal static string PropertyOfTypeCannotBeNull + { + get => GetString("PropertyOfTypeCannotBeNull"); + } + + /// + /// The '{0}' property of '{1}' must not be null. + /// + internal static string FormatPropertyOfTypeCannotBeNull(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("PropertyOfTypeCannotBeNull"), p0, p1); + + /// + /// Page created by '{0}' must be an instance of '{1}'. + /// + internal static string ActivatedInstance_MustBeAnInstanceOf + { + get => GetString("ActivatedInstance_MustBeAnInstanceOf"); + } + + /// + /// Page created by '{0}' must be an instance of '{1}'. + /// + internal static string FormatActivatedInstance_MustBeAnInstanceOf(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ActivatedInstance_MustBeAnInstanceOf"), p0, p1); + + /// + /// The context used to execute '{0}' must be an instance of '{1}'. Returning a '{2}' from a controller is a not supported. + /// + internal static string PageViewResult_ContextIsInvalid + { + get => GetString("PageViewResult_ContextIsInvalid"); + } + + /// + /// The context used to execute '{0}' must be an instance of '{1}'. Returning a '{2}' from a controller is a not supported. + /// + internal static string FormatPageViewResult_ContextIsInvalid(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("PageViewResult_ContextIsInvalid"), p0, p1, p2); + + /// + /// Value cannot be null or empty. + /// + internal static string ArgumentCannotBeNullOrEmpty + { + get => GetString("ArgumentCannotBeNullOrEmpty"); + } + + /// + /// Value cannot be null or empty. + /// + internal static string FormatArgumentCannotBeNullOrEmpty() + => GetString("ArgumentCannotBeNullOrEmpty"); + + /// + /// Unsupported handler method return type '{0}'. + /// + internal static string UnsupportedHandlerMethodType + { + get => GetString("UnsupportedHandlerMethodType"); + } + + /// + /// Unsupported handler method return type '{0}'. + /// + internal static string FormatUnsupportedHandlerMethodType(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedHandlerMethodType"), p0); + + /// + /// Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:{0}{0}{1} + /// + internal static string AmbiguousHandler + { + get => GetString("AmbiguousHandler"); + } + + /// + /// Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:{0}{0}{1} + /// + internal static string FormatAmbiguousHandler(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("AmbiguousHandler"), p0, p1); + + /// + /// Path must be a root relative path that starts with a forward slash '/'. + /// + internal static string PathMustBeRootRelativePath + { + get => GetString("PathMustBeRootRelativePath"); + } + + /// + /// Path must be a root relative path that starts with a forward slash '/'. + /// + internal static string FormatPathMustBeRootRelativePath() + => GetString("PathMustBeRootRelativePath"); + + /// + /// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}. + /// + internal static string AsyncPageFilter_InvalidShortCircuit + { + get => GetString("AsyncPageFilter_InvalidShortCircuit"); + } + + /// + /// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}. + /// + internal static string FormatAsyncPageFilter_InvalidShortCircuit(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("AsyncPageFilter_InvalidShortCircuit"), p0, p1, p2, p3); + + /// + /// The type '{0}' is not a valid page. A page must inherit from '{1}'. + /// + internal static string InvalidPageType_WrongBase + { + get => GetString("InvalidPageType_WrongBase"); + } + + /// + /// The type '{0}' is not a valid page. A page must inherit from '{1}'. + /// + internal static string FormatInvalidPageType_WrongBase(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidPageType_WrongBase"), p0, p1); + + /// + /// The type '{0}' is not a valid page. A page must define a public, non-static '{1}' property. + /// + internal static string InvalidPageType_NoModelProperty + { + get => GetString("InvalidPageType_NoModelProperty"); + } + + /// + /// The type '{0}' is not a valid page. A page must define a public, non-static '{1}' property. + /// + internal static string FormatInvalidPageType_NoModelProperty(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidPageType_NoModelProperty"), p0, p1); + + /// + /// '{0}' is not a valid page name. A page name is path relative to the Razor Pages root directory that starts with a leading forward slash ('/') and does not contain the file extension e.g "/Users/Edit". + /// + internal static string InvalidValidPageName + { + get => GetString("InvalidValidPageName"); + } + + /// + /// '{0}' is not a valid page name. A page name is path relative to the Razor Pages root directory that starts with a leading forward slash ('/') and does not contain the file extension e.g "/Users/Edit". + /// + internal static string FormatInvalidValidPageName(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidValidPageName"), p0); + + /// + /// The model type for '{0}' is of type '{1}' which is not assignable to its declared model type '{2}'. + /// + internal static string InvalidActionDescriptorModelType + { + get => GetString("InvalidActionDescriptorModelType"); + } + + /// + /// The model type for '{0}' is of type '{1}' which is not assignable to its declared model type '{2}'. + /// + internal static string FormatInvalidActionDescriptorModelType(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidActionDescriptorModelType"), 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/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs new file mode 100644 index 0000000000..b3b0b15e11 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs @@ -0,0 +1,144 @@ +// Copyright (c) .NET 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 Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + /// + /// Provides configuration for RazorPages. + /// + public class RazorPagesOptions : IEnumerable + { + private readonly CompatibilitySwitch _allowAreas; + private readonly CompatibilitySwitch _allowMappingHeadRequestsToGetHandler; + private readonly ICompatibilitySwitch[] _switches; + + private string _root = "/Pages"; + + public RazorPagesOptions() + { + _allowAreas = new CompatibilitySwitch(nameof(AllowAreas)); + _allowMappingHeadRequestsToGetHandler = new CompatibilitySwitch(nameof(AllowMappingHeadRequestsToGetHandler)); + + _switches = new ICompatibilitySwitch[] + { + _allowAreas, + _allowMappingHeadRequestsToGetHandler, + }; + } + + /// + /// Gets a collection of instances that are applied during + /// route and page model construction. + /// + public PageConventionCollection Conventions { get; } = new PageConventionCollection(); + + /// + /// Application relative path used as the root of discovery for Razor Page files. + /// Defaults to the /Pages directory under application root. + /// + public string RootDirectory + { + get => _root; + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(value)); + } + + if (value[0] != '/') + { + throw new ArgumentException(Resources.PathMustBeRootRelativePath, nameof(value)); + } + + _root = value; + } + } + + /// + /// Gets or sets a value that determines if areas are enabled for Razor Pages. + /// Defaults to false. + /// + /// + /// + /// When enabled, any Razor Page under the directory structure /Area/AreaName/Pages/ + /// will be associated with an area with the name AreaName. + /// + /// + /// 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 false unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have value true unless explicitly configured. + /// + /// + public bool AllowAreas + { + get => _allowAreas.Value; + set => _allowAreas.Value = value; + } + + /// + /// Gets or sets a value that determines if HTTP method matching for Razor Pages handler methods will use + /// fuzzy matching. + /// Defaults to false. + /// + /// + /// + /// When enabled, Razor Pages handler methods will be more flexible in which HTTP methods will be accepted + /// by GET and POST handler methods. This allows a GET handler methods to accept the HEAD HTTP methods in + /// addition to GET. A more specific handler method can still be defined to accept HEAD, and the most + /// specific handler will be invoked. + /// + /// + /// This setting reduces the number of handler methods that must be written to correctly respond to typical + /// web traffic including requests from internet infrastructure such as web crawlers. + /// + /// + /// 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 false unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have value true unless explicitly configured. + /// + /// + public bool AllowMappingHeadRequestsToGetHandler + { + get => _allowMappingHeadRequestsToGetHandler.Value; + set => _allowMappingHeadRequestsToGetHandler.Value = value; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_switches).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator(); + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptionsConfigureCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptionsConfigureCompatibilityOptions.cs new file mode 100644 index 0000000000..8f5d21d8bc --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptionsConfigureCompatibilityOptions.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.Infrastructure; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + internal class RazorPagesOptionsConfigureCompatibilityOptions : ConfigureCompatibilityOptions + { + public RazorPagesOptionsConfigureCompatibilityOptions( + ILoggerFactory loggerFactory, + IOptions compatibilityOptions) + : base(loggerFactory, compatibilityOptions) + { + } + + protected override IReadOnlyDictionary DefaultValues + { + get + { + var values = new Dictionary(); + + if (Version >= CompatibilityVersion.Version_2_1) + { + values[nameof(RazorPagesOptions.AllowAreas)] = true; + values[nameof(RazorPagesOptions.AllowMappingHeadRequestsToGetHandler)] = true; + } + + return values; + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx new file mode 100644 index 0000000000..2d9e11cd41 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 '{0}' property of '{1}' must not be null. + + + Page created by '{0}' must be an instance of '{1}'. + + + The context used to execute '{0}' must be an instance of '{1}'. Returning a '{2}' from a controller is a not supported. + + + Value cannot be null or empty. + + + Unsupported handler method return type '{0}'. + + + Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:{0}{0}{1} + + + Path must be a root relative path that starts with a forward slash '/'. + + + If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}. + + + The type '{0}' is not a valid page. A page must inherit from '{1}'. + + + The type '{0}' is not a valid page. A page must define a public, non-static '{1}' property. + + + '{0}' is not a valid page name. A page name is path relative to the Razor Pages root directory that starts with a leading forward slash ('/') and does not contain the file extension e.g "/Users/Edit". + + + The model type for '{0}' is of type '{1}' which is not assignable to its declared model type '{2}'. + + \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/baseline.netcore.json new file mode 100644 index 0000000000..7a48f0e5f8 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/baseline.netcore.json @@ -0,0 +1,7982 @@ +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.RazorPages, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IAsyncPageFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "OnPageHandlerSelectionAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerSelectedContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "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", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [ + { + "Kind": "Method", + "Name": "OnPageHandlerSelected", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerSelectedContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnPageHandlerExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutingContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnPageHandlerExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutedContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutedContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionDescriptor", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Canceled", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Canceled", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerInstance", + "Parameters": [], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerMethod", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Exception", + "Parameters": [], + "ReturnType": "System.Exception", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Exception", + "Parameters": [ + { + "Name": "value", + "Type": "System.Exception" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExceptionDispatchInfo", + "Parameters": [], + "ReturnType": "System.Runtime.ExceptionServices.ExceptionDispatchInfo", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ExceptionDispatchInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Runtime.ExceptionServices.ExceptionDispatchInfo" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExceptionHandled", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ExceptionHandled", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IActionResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageContext", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "handlerMethod", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor" + }, + { + "Name": "handlerInstance", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutingContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionDescriptor", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Result", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IActionResult" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerArguments", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerMethod", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerInstance", + "Parameters": [], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageContext", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "handlerMethod", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor" + }, + { + "Name": "handlerArguments", + "Type": "System.Collections.Generic.IDictionary" + }, + { + "Name": "handlerInstance", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutionDelegate", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.MulticastDelegate", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Invoke", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BeginInvoke", + "Parameters": [ + { + "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.Filters.PageHandlerSelectedContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Filters.FilterContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionDescriptor", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerMethod", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HandlerMethod", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerInstance", + "Parameters": [], + "ReturnType": "System.Object", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageContext", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + }, + { + "Name": "filters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "handlerInstance", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_HandlerMethods", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HandlerMethods", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerTypeInfo", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HandlerTypeInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.TypeInfo" + } + ], + "ReturnType": "System.Void", + "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", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelTypeInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.TypeInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PageTypeInfo", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PageTypeInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.TypeInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.IPageActivatorProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateActivator", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Func", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateReleaser", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Action", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.IPageFactoryProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreatePageFactory", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Func", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreatePageDisposer", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Action", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.IPageModelActivatorProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateActivator", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Func", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateReleaser", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Action", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.IPageModelFactoryProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateModelFactory", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Func", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateModelDisposer", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Action", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.NonHandlerAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Page", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "Microsoft.AspNetCore.Mvc.RazorPages.PageBase", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor", + "ImplementedInterfaces": [], + "Members": [ + { + "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_ViewEnginePath", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewEnginePath", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "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", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DisplayName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.PageBase", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "Microsoft.AspNetCore.Mvc.Razor.RazorPageBase", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ViewContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Request", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpRequest", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Response", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpResponse", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteData", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteData", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary", + "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, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EndContext", + "Parameters": [], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Challenge", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Challenge", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Challenge", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "contentEncoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "virtualPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "virtualPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "PhysicalFile", + "Parameters": [ + { + "Name": "physicalPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "PhysicalFile", + "Parameters": [ + { + "Name": "physicalPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirect", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirectPermanent", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirectPreserveMethod", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirectPermanentPreserveMethod", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "NotFound", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.NotFoundResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "NotFound", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.NotFoundObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Page", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.PageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Redirect", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectPermanent", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectPreserveMethod", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectPermanentPreserveMethod", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPreserveMethod", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "controllerName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanentPreserveMethod", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "controllerName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePreserveMethod", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanentPreserveMethod", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePreserveMethod", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "pageHandler", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanentPreserveMethod", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "pageHandler", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignIn", + "Parameters": [ + { + "Name": "principal", + "Type": "System.Security.Claims.ClaimsPrincipal" + }, + { + "Name": "authenticationScheme", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignInResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignIn", + "Parameters": [ + { + "Name": "principal", + "Type": "System.Security.Claims.ClaimsPrincipal" + }, + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationScheme", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignInResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignOut", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignOutResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignOut", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignOutResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "StatusCode", + "Parameters": [ + { + "Name": "statusCode", + "Type": "System.Int32" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "StatusCode", + "Parameters": [ + { + "Name": "statusCode", + "Type": "System.Int32" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Unauthorized", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.UnauthorizedResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "model", + "Type": "T0" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": 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" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": 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": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": 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": "includeExpressions", + "Type": "System.Linq.Expressions.Expression>[]", + "IsParams": true + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "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": "propertyFilter", + "Type": "System.Func" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "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": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "includeExpressions", + "Type": "System.Linq.Expressions.Expression>[]", + "IsParams": true + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "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": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "propertyFilter", + "Type": "System.Func" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "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" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": 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": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "propertyFilter", + "Type": "System.Func" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryValidateModel", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryValidateModel", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + }, + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ActionContext", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionDescriptor", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionDescriptor", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueProviderFactories", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValueProviderFactories", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewData", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewData", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewStartFactories", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList>", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewStartFactories", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList>" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.PageContextAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.PageModel", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IAsyncPageFilter", + "Microsoft.AspNetCore.Mvc.Filters.IPageFilter" + ], + "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_HttpContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Request", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpRequest", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Response", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Http.HttpResponse", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteData", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Routing.RouteData", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_User", + "Parameters": [], + "ReturnType": "System.Security.Claims.ClaimsPrincipal", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_TempData", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_TempData", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Url", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IUrlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Url", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.IUrlHelper" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewData", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "model", + "Type": "T0" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Protected", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "model", + "Type": "T0" + }, + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Protected", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "model", + "Type": "T0" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Protected", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "model", + "Type": "T0" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "includeExpressions", + "Type": "System.Linq.Expressions.Expression>[]", + "IsParams": true + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Protected", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "model", + "Type": "T0" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "propertyFilter", + "Type": "System.Func" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Protected", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "model", + "Type": "T0" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "includeExpressions", + "Type": "System.Linq.Expressions.Expression>[]", + "IsParams": true + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Protected", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "model", + "Type": "T0" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "propertyFilter", + "Type": "System.Func" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Protected", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + }, + { + "Name": "modelType", + "Type": "System.Type" + }, + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + }, + { + "Name": "modelType", + "Type": "System.Type" + }, + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "valueProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + }, + { + "Name": "propertyFilter", + "Type": "System.Func" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "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", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Challenge", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Challenge", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Challenge", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ChallengeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "contentEncoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Content", + "Parameters": [ + { + "Name": "content", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "Microsoft.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Forbid", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ForbidResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "virtualPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "virtualPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirect", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirectPermanent", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirectPreserveMethod", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "LocalRedirectPermanentPreserveMethod", + "Parameters": [ + { + "Name": "localUrl", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.LocalRedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "NotFound", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.NotFoundResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "NotFound", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.NotFoundObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Page", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.PageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "PhysicalFile", + "Parameters": [ + { + "Name": "physicalPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "PhysicalFile", + "Parameters": [ + { + "Name": "physicalPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Redirect", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectPermanent", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectPreserveMethod", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectPermanentPreserveMethod", + "Parameters": [ + { + "Name": "url", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPreserveMethod", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "controllerName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanent", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String" + }, + { + "Name": "controllerName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToActionPermanentPreserveMethod", + "Parameters": [ + { + "Name": "actionName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "controllerName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoute", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePreserveMethod", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanent", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToRoutePermanentPreserveMethod", + "Parameters": [ + { + "Name": "routeName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPage", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanent", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "pageHandler", + "Type": "System.String" + }, + { + "Name": "routeValues", + "Type": "System.Object" + }, + { + "Name": "fragment", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePreserveMethod", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "pageHandler", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RedirectToPagePermanentPreserveMethod", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "pageHandler", + "Type": "System.String", + "DefaultValue": "null" + }, + { + "Name": "routeValues", + "Type": "System.Object", + "DefaultValue": "null" + }, + { + "Name": "fragment", + "Type": "System.String", + "DefaultValue": "null" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToPageResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignIn", + "Parameters": [ + { + "Name": "principal", + "Type": "System.Security.Claims.ClaimsPrincipal" + }, + { + "Name": "authenticationScheme", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignInResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignIn", + "Parameters": [ + { + "Name": "principal", + "Type": "System.Security.Claims.ClaimsPrincipal" + }, + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationScheme", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignInResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignOut", + "Parameters": [ + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignOutResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SignOut", + "Parameters": [ + { + "Name": "properties", + "Type": "Microsoft.AspNetCore.Authentication.AuthenticationProperties" + }, + { + "Name": "authenticationSchemes", + "Type": "System.String[]", + "IsParams": true + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.SignOutResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "StatusCode", + "Parameters": [ + { + "Name": "statusCode", + "Type": "System.Int32" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "StatusCode", + "Parameters": [ + { + "Name": "statusCode", + "Type": "System.Int32" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Unauthorized", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.UnauthorizedResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryValidateModel", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryValidateModel", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + }, + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "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", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.PageResult", + "Visibility": "Public", + "Kind": "Class", + "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", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ContentType", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Model", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Page", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.PageBase", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Page", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageBase" + } + ], + "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_StatusCode", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_StatusCode", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.RazorPagesOptions", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Conventions", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RootDirectory", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RootDirectory", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AllowAreas", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AllowAreas", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AllowMappingHeadRequestsToGetHandler", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AllowMappingHeadRequestsToGetHandler", + "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.RazorPages.Infrastructure.DefaultPageActivatorProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.RazorPages.IPageActivatorProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CreateActivator", + "Parameters": [ + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Func", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.RazorPages.IPageActivatorProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateReleaser", + "Parameters": [ + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Action", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.RazorPages.IPageActivatorProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.DefaultPageFactoryProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.RazorPages.IPageFactoryProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CreatePageFactory", + "Parameters": [ + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Func", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.RazorPages.IPageFactoryProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreatePageDisposer", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Action", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.RazorPages.IPageFactoryProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageActivator", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.IPageActivatorProvider" + }, + { + "Name": "metadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "urlHelperFactory", + "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" + }, + { + "Name": "jsonHelper", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper" + }, + { + "Name": "diagnosticSource", + "Type": "System.Diagnostics.DiagnosticSource" + }, + { + "Name": "htmlEncoder", + "Type": "System.Text.Encodings.Web.HtmlEncoder" + }, + { + "Name": "modelExpressionProvider", + "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.DefaultPageModelActivatorProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.RazorPages.IPageModelActivatorProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CreateActivator", + "Parameters": [ + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Func", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.RazorPages.IPageModelActivatorProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateReleaser", + "Parameters": [ + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Action", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.RazorPages.IPageModelActivatorProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.DefaultPageModelFactoryProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.RazorPages.IPageModelFactoryProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CreateModelFactory", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Func", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.RazorPages.IPageModelFactoryProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateModelDisposer", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" + } + ], + "ReturnType": "System.Action", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.RazorPages.IPageModelFactoryProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "modelActivator", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.IPageModelActivatorProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_MethodInfo", + "Parameters": [], + "ReturnType": "System.Reflection.MethodInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MethodInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.MethodInfo" + } + ], + "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_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_Parameters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Parameters", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerParameterDescriptor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ParameterInfo", + "Parameters": [], + "ReturnType": "System.Reflection.ParameterInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ParameterInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.ParameterInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.IPageHandlerMethodSelector", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Select", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.IPageLoader", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Load", + "Parameters": [ + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionDescriptorProvider", + "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": "set_Order", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "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": "BuildModel", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Protected", + "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": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageRouteModelProviders", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "mvcOptionsAccessor", + "Type": "Microsoft.Extensions.Options.IOptions" + }, + { + "Name": "pagesOptionsAccessor", + "Type": "Microsoft.Extensions.Options.IOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageArgumentBinder", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "BindModelAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + }, + { + "Name": "type", + "Type": "System.Type" + }, + { + "Name": "default", + "Type": "System.Object" + }, + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BindModelAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + }, + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "BindModelAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + }, + { + "Name": "default", + "Type": "T0" + }, + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + }, + { + "Name": "value", + "Type": "T0" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "TryUpdateModelAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + }, + { + "Name": "value", + "Type": "T0" + }, + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "BindAsync", + "Parameters": [ + { + "Name": "context", + "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, + "Abstract": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageBoundPropertyDescriptor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Property", + "Parameters": [], + "ReturnType": "System.Reflection.PropertyInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Property", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.PropertyInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageDirectiveFeature", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "TryGetPageDirective", + "Parameters": [ + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + }, + { + "Name": "projectItem", + "Type": "Microsoft.AspNetCore.Razor.Language.RazorProjectItem" + }, + { + "Name": "template", + "Type": "System.String", + "Direction": "Out" + } + ], + "ReturnType": "System.Boolean", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageModelAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "pageContext", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "writerFactory", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory" + }, + { + "Name": "compositeViewEngine", + "Type": "Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine" + }, + { + "Name": "razorViewEngine", + "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine" + }, + { + "Name": "razorPageActivator", + "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorPageActivator" + }, + { + "Name": "diagnosticSource", + "Type": "System.Diagnostics.DiagnosticSource" + }, + { + "Name": "htmlEncoder", + "Type": "System.Text.Encodings.Web.HtmlEncoder" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageViewLocationExpander", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Razor.IViewLocationExpander" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExpandViewLocations", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Razor.ViewLocationExpanderContext" + }, + { + "Name": "viewLocations", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IViewLocationExpander", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "PopulateValues", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Razor.ViewLocationExpanderContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IViewLocationExpander", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAdapter", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Razor.IRazorPage" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ViewContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BodyContent", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BodyContent", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Html.IHtmlContent" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsLayoutBeingRendered", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsLayoutBeingRendered", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Path", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Path", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Layout", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Layout", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PreviousSectionWriters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PreviousSectionWriters", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IDictionary" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SectionWriters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EnsureRenderedBodyOrSections", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [], + "ReturnType": "System.Threading.Tasks.Task", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "page", + "Type": "Microsoft.AspNetCore.Mvc.Razor.RazorPageBase" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.RazorPageAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_RouteTemplate", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "path", + "Type": "System.String" + }, + { + "Name": "viewType", + "Type": "System.Type" + }, + { + "Name": "routeTemplate", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.DefaultPageHandlerMethodSelector", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.IPageHandlerMethodSelector" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Select", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.IPageHandlerMethodSelector", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.Extensions.Options.IOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelConvention", + "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.PageApplicationModel" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", + "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.ApplicationModels.PageApplicationModelProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModelProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageConvention", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "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", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageConvention" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Apply", + "Parameters": [ + { + "Name": "model", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteModel" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelProvider", + "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.ApplicationModels.PageRouteModelProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteModelProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionDescriptor", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RelativePath", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewEnginePath", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AreaName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteTemplate", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Filters", + "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_PageType", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PageType", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.TypeInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DeclaredModelType", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelType", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelType", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.TypeInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerType", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerTypeAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerMethods", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandlerProperties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor" + }, + { + "Name": "handlerType", + "Type": "System.Reflection.TypeInfo" + }, + { + "Name": "handlerAttributes", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "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", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModelProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionDescriptor", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PageType", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PageApplicationModel", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PageApplicationModel", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor" + }, + { + "Name": "pageTypeInfo", + "Type": "System.Reflection.TypeInfo" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Collections.ObjectModel.Collection", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddPageApplicationModelConvention", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "action", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelConvention", + "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", + "Parameters": [ + { + "Name": "folderPath", + "Type": "System.String" + }, + { + "Name": "action", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelConvention", + "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", + "Parameters": [ + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "action", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelConvention", + "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", + "Parameters": [ + { + "Name": "folderPath", + "Type": "System.String" + }, + { + "Name": "action", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelConvention", + "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", + "Parameters": [], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TPageConvention", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageConvention" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "RemoveType", + "Parameters": [ + { + "Name": "pageConventionType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "conventions", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageHandlerModel", + "Visibility": "Public", + "Kind": "Class", + "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_MethodInfo", + "Parameters": [], + "ReturnType": "System.Reflection.MethodInfo", + "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_HandlerName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HandlerName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Parameters", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Page", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Page", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel" + } + ], + "ReturnType": "System.Void", + "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": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "handlerMethod", + "Type": "System.Reflection.MethodInfo" + }, + { + "Name": "attributes", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageHandlerModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageParameterModel", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModelBase", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Handler", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageHandlerModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Handler", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageHandlerModel" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ParameterInfo", + "Parameters": [], + "ReturnType": "System.Reflection.ParameterInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ParameterName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ParameterName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "parameterInfo", + "Type": "System.Reflection.ParameterInfo" + }, + { + "Name": "attributes", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageParameterModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModelBase", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Page", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Page", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyInfo", + "Parameters": [], + "ReturnType": "System.Reflection.PropertyInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PropertyName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PropertyName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "propertyInfo", + "Type": "System.Reflection.PropertyInfo" + }, + { + "Name": "attributes", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteModel", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_RelativePath", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewEnginePath", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AreaName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Selectors", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "relativePath", + "Type": "System.String" + }, + { + "Name": "viewEnginePath", + "Type": "System.String" + } + ], + "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", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteModel" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteModelProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_RouteModels", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcRazorPagesMvcBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddRazorPagesOptions", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WithRazorPagesRoot", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "rootDirectory", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WithRazorPagesAtContentRoot", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.MvcRazorPagesMvcCoreBuilderExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "AddRazorPages", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddRazorPages", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "setupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WithRazorPagesRoot", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "rootDirectory", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.Extensions.DependencyInjection.PageConventionCollectionExtensions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "Static": true, + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "ConfigureFilter", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "factory", + "Type": "System.Func" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelConvention", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConfigureFilter", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "filter", + "Type": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AllowAnonymousToPage", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "Extension": true, + "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", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "folderPath", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "Extension": true, + "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", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "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": "AuthorizePage", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "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" + } + ], + "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", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "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": "AuthorizeFolder", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "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" + } + ], + "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", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "route", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "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": [] + } + ] +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs new file mode 100644 index 0000000000..175523d2fb --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs @@ -0,0 +1,283 @@ +// Copyright (c) .NET 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.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers +{ + /// + /// implementation targeting <a> elements. + /// + [HtmlTargetElement("a", Attributes = ActionAttributeName)] + [HtmlTargetElement("a", Attributes = ControllerAttributeName)] + [HtmlTargetElement("a", Attributes = AreaAttributeName)] + [HtmlTargetElement("a", Attributes = PageAttributeName)] + [HtmlTargetElement("a", Attributes = PageHandlerAttributeName)] + [HtmlTargetElement("a", Attributes = FragmentAttributeName)] + [HtmlTargetElement("a", Attributes = HostAttributeName)] + [HtmlTargetElement("a", Attributes = ProtocolAttributeName)] + [HtmlTargetElement("a", Attributes = RouteAttributeName)] + [HtmlTargetElement("a", Attributes = RouteValuesDictionaryName)] + [HtmlTargetElement("a", Attributes = RouteValuesPrefix + "*")] + public class AnchorTagHelper : TagHelper + { + private const string ActionAttributeName = "asp-action"; + private const string ControllerAttributeName = "asp-controller"; + private const string AreaAttributeName = "asp-area"; + private const string PageAttributeName = "asp-page"; + private const string PageHandlerAttributeName = "asp-page-handler"; + private const string FragmentAttributeName = "asp-fragment"; + private const string HostAttributeName = "asp-host"; + private const string ProtocolAttributeName = "asp-protocol"; + private const string RouteAttributeName = "asp-route"; + private const string RouteValuesDictionaryName = "asp-all-route-data"; + private const string RouteValuesPrefix = "asp-route-"; + private const string Href = "href"; + private IDictionary _routeValues; + + /// + /// Creates a new . + /// + /// The . + public AnchorTagHelper(IHtmlGenerator generator) + { + Generator = generator; + } + + /// + public override int Order => -1000; + + protected IHtmlGenerator Generator { get; } + + /// + /// The name of the action method. + /// + /// + /// Must be null if or is non-null. + /// + [HtmlAttributeName(ActionAttributeName)] + public string Action { get; set; } + + /// + /// The name of the controller. + /// + /// + /// Must be null if or is non-null. + /// + [HtmlAttributeName(ControllerAttributeName)] + public string Controller { get; set; } + + /// + /// The name of the area. + /// + /// + /// Must be null if or is non-null. + /// + [HtmlAttributeName(AreaAttributeName)] + public string Area { get; set; } + + /// + /// The name of the page. + /// + /// + /// Must be null if or , + /// or is non-null. + /// + [HtmlAttributeName(PageAttributeName)] + public string Page { get; set; } + + /// + /// The name of the page handler. + /// + /// + /// Must be null if or , or + /// is non-null. + /// + [HtmlAttributeName(PageHandlerAttributeName)] + public string PageHandler { get; set; } + + /// + /// The protocol for the URL, such as "http" or "https". + /// + [HtmlAttributeName(ProtocolAttributeName)] + public string Protocol { get; set; } + + /// + /// The host name. + /// + [HtmlAttributeName(HostAttributeName)] + public string Host { get; set; } + + /// + /// The URL fragment name. + /// + [HtmlAttributeName(FragmentAttributeName)] + public string Fragment { get; set; } + + /// + /// Name of the route. + /// + /// + /// Must be null if one of , , + /// or is non-null. + /// + [HtmlAttributeName(RouteAttributeName)] + public string Route { get; set; } + + /// + /// Additional parameters for the route. + /// + [HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)] + public IDictionary RouteValues + { + get + { + if (_routeValues == null) + { + _routeValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + return _routeValues; + } + set + { + _routeValues = value; + } + } + + /// + /// Gets or sets the for the current request. + /// + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + + /// + /// Does nothing if user provides an href attribute. + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + // If "href" is already set, it means the user is attempting to use a normal anchor. + if (output.Attributes.ContainsName(Href)) + { + if (Action != null || + Controller != null || + Area != null || + Page != null || + PageHandler != null || + Route != null || + Protocol != null || + Host != null || + Fragment != null || + (_routeValues != null && _routeValues.Count > 0)) + { + // User specified an href and one of the bound attributes; can't determine the href attribute. + throw new InvalidOperationException( + Resources.FormatAnchorTagHelper_CannotOverrideHref( + Href, + "", + RouteValuesPrefix, + ActionAttributeName, + ControllerAttributeName, + AreaAttributeName, + RouteAttributeName, + ProtocolAttributeName, + HostAttributeName, + FragmentAttributeName, + PageAttributeName, + PageHandlerAttributeName)); + } + + return; + } + + var routeLink = Route != null; + var actionLink = Controller != null || Action != null; + var pageLink = Page != null || PageHandler != null; + + if ((routeLink && actionLink) || (routeLink && pageLink) || (actionLink && pageLink)) + { + var message = string.Join( + Environment.NewLine, + Resources.FormatCannotDetermineAttributeFor(Href, ""), + RouteAttributeName, + ControllerAttributeName + ", " + ActionAttributeName, + PageAttributeName + ", " + PageHandlerAttributeName); + + throw new InvalidOperationException(message); + } + + RouteValueDictionary routeValues = null; + if (_routeValues != null && _routeValues.Count > 0) + { + routeValues = new RouteValueDictionary(_routeValues); + } + + if (Area != null) + { + // Unconditionally replace any value from asp-route-area. + if (routeValues == null) + { + routeValues = new RouteValueDictionary(); + } + routeValues["area"] = Area; + } + + TagBuilder tagBuilder; + if (pageLink) + { + tagBuilder = Generator.GeneratePageLink( + ViewContext, + linkText: string.Empty, + pageName: Page, + pageHandler: PageHandler, + protocol: Protocol, + hostname: Host, + fragment: Fragment, + routeValues: routeValues, + htmlAttributes: null); + } + else if (routeLink) + { + tagBuilder = Generator.GenerateRouteLink( + ViewContext, + linkText: string.Empty, + routeName: Route, + protocol: Protocol, + hostName: Host, + fragment: Fragment, + routeValues: routeValues, + htmlAttributes: null); + } + else + { + tagBuilder = Generator.GenerateActionLink( + ViewContext, + linkText: string.Empty, + actionName: Action, + controllerName: Controller, + protocol: Protocol, + hostname: Host, + fragment: Fragment, + routeValues: routeValues, + htmlAttributes: null); + } + + output.MergeAttributes(tagBuilder); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs new file mode 100644 index 0000000000..f1d6730ae0 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs @@ -0,0 +1,326 @@ +// Copyright (c) .NET 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.Mvc.TagHelpers.Internal; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Internal; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache +{ + /// + /// An instance of represents the state of + /// or keys. + /// + public class CacheTagKey : IEquatable + { + private static readonly char[] AttributeSeparator = new[] { ',' }; + private static readonly Func CookieAcccessor = (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(); + + private const string CacheKeyTokenSeparator = "||"; + private const string VaryByName = "VaryBy"; + private const string VaryByHeaderName = "VaryByHeader"; + private const string VaryByQueryName = "VaryByQuery"; + private const string VaryByRouteName = "VaryByRoute"; + private const string VaryByCookieName = "VaryByCookie"; + private const string VaryByUserName = "VaryByUser"; + + private readonly string _prefix; + private readonly string _varyBy; + private readonly DateTimeOffset? _expiresOn; + private readonly TimeSpan? _expiresAfter; + private readonly TimeSpan? _expiresSliding; + private readonly IList> _headers; + private readonly IList> _queries; + private readonly IList> _routeValues; + private readonly IList> _cookies; + private readonly bool _varyByUser; + private readonly string _username; + + private string _generatedKey; + private int? _hashcode; + + /// + /// Creates an instance of for a specific . + /// + /// The . + /// The . + /// A new . + public CacheTagKey(CacheTagHelper tagHelper, TagHelperContext context) + : this(tagHelper) + { + Key = context.UniqueId; + _prefix = nameof(CacheTagHelper); + } + + /// + /// Creates an instance of for a specific . + /// + /// The . + /// A new . + public CacheTagKey(DistributedCacheTagHelper tagHelper) + : this((CacheTagHelperBase)tagHelper) + { + Key = tagHelper.Name; + _prefix = nameof(DistributedCacheTagHelper); + } + + private CacheTagKey(CacheTagHelperBase tagHelper) + { + var httpContext = tagHelper.ViewContext.HttpContext; + var request = httpContext.Request; + + _expiresAfter = tagHelper.ExpiresAfter; + _expiresOn = tagHelper.ExpiresOn; + _expiresSliding = tagHelper.ExpiresSliding; + _varyBy = tagHelper.VaryBy; + _cookies = ExtractCollection(tagHelper.VaryByCookie, request.Cookies, CookieAcccessor); + _headers = ExtractCollection(tagHelper.VaryByHeader, request.Headers, HeaderAccessor); + _queries = ExtractCollection(tagHelper.VaryByQuery, request.Query, QueryAccessor); + _routeValues = ExtractCollection(tagHelper.VaryByRoute, tagHelper.ViewContext.RouteData.Values, RouteValueAccessor); + _varyByUser = tagHelper.VaryByUser; + + if (_varyByUser) + { + _username = httpContext.User?.Identity?.Name; + } + } + + // Internal for unit testing. + internal string Key { get; } + + /// + /// Creates a representation of the key. + /// + /// A uniquely representing the key. + public string GenerateKey() + { + // Caching as the key is immutable and it can be called multiple times during a request. + if (_generatedKey != null) + { + return _generatedKey; + } + + var builder = new StringBuilder(_prefix); + builder + .Append(CacheKeyTokenSeparator) + .Append(Key); + + if (!string.IsNullOrEmpty(_varyBy)) + { + builder + .Append(CacheKeyTokenSeparator) + .Append(VaryByName) + .Append(CacheKeyTokenSeparator) + .Append(_varyBy); + } + + AddStringCollection(builder, VaryByCookieName, _cookies); + AddStringCollection(builder, VaryByHeaderName, _headers); + AddStringCollection(builder, VaryByQueryName, _queries); + AddStringCollection(builder, VaryByRouteName, _routeValues); + + if (_varyByUser) + { + builder + .Append(CacheKeyTokenSeparator) + .Append(VaryByUserName) + .Append(CacheKeyTokenSeparator) + .Append(_username); + } + + _generatedKey = builder.ToString(); + + return _generatedKey; + } + + /// + /// Creates a hashed value of the key. + /// + /// A cryptographic hash of the key. + public string GenerateHashedKey() + { + var key = GenerateKey(); + + // The key is typically too long to be useful, so we use a cryptographic hash + // as the actual key (better randomization and key distribution, so small vary + // values will generate dramatically different keys). + using (var sha256 = CryptographyAlgorithms.CreateSHA256()) + { + var contentBytes = Encoding.UTF8.GetBytes(key); + var hashedBytes = sha256.ComputeHash(contentBytes); + return Convert.ToBase64String(hashedBytes); + } + } + + /// + public override bool Equals(object obj) + { + var other = obj as CacheTagKey; + if (other == null) + { + return false; + } + + return Equals(other); + } + + /// + public bool Equals(CacheTagKey other) + { + return string.Equals(other.Key, Key, StringComparison.Ordinal) && + other._expiresAfter == _expiresAfter && + other._expiresOn == _expiresOn && + other._expiresSliding == _expiresSliding && + string.Equals(other._varyBy, _varyBy, StringComparison.Ordinal) && + AreSame(_cookies, other._cookies) && + AreSame(_headers, other._headers) && + AreSame(_queries, other._queries) && + AreSame(_routeValues, other._routeValues) && + _varyByUser == other._varyByUser && + (!_varyByUser || string.Equals(other._username, _username, StringComparison.Ordinal)); + } + + /// + public override int GetHashCode() + { + // The hashcode is intentionally not using the computed + // stringified key in order to prevent string allocations + // in the common case where it's not explicitly required. + + // Caching as the key is immutable and it can be called + // multiple times during a request. + if (_hashcode.HasValue) + { + return _hashcode.Value; + } + + var hashCodeCombiner = new HashCodeCombiner(); + + hashCodeCombiner.Add(Key, StringComparer.Ordinal); + hashCodeCombiner.Add(_expiresAfter); + hashCodeCombiner.Add(_expiresOn); + hashCodeCombiner.Add(_expiresSliding); + hashCodeCombiner.Add(_varyBy, StringComparer.Ordinal); + hashCodeCombiner.Add(_username, StringComparer.Ordinal); + + CombineCollectionHashCode(hashCodeCombiner, VaryByCookieName, _cookies); + CombineCollectionHashCode(hashCodeCombiner, VaryByHeaderName, _headers); + CombineCollectionHashCode(hashCodeCombiner, VaryByQueryName, _queries); + CombineCollectionHashCode(hashCodeCombiner, VaryByRouteName, _routeValues); + + _hashcode = hashCodeCombiner.CombinedHash; + + return _hashcode.Value; + } + + private static IList> ExtractCollection(string keys, TSourceCollection collection, Func accessor) + { + if (string.IsNullOrEmpty(keys)) + { + return null; + } + + var tokenizer = new StringTokenizer(keys, AttributeSeparator); + + var result = new List>(); + + foreach (var item in tokenizer) + { + var trimmedValue = item.Trim(); + + if (trimmedValue.Length != 0) + { + var value = accessor(collection, trimmedValue.Value); + result.Add(new KeyValuePair(trimmedValue.Value, value ?? string.Empty)); + } + } + + return result; + } + + private static void AddStringCollection( + StringBuilder builder, + string collectionName, + IList> values) + { + if (values == null || values.Count == 0) + { + return; + } + + // keyName(param1=value1|param2=value2) + builder + .Append(CacheKeyTokenSeparator) + .Append(collectionName) + .Append("("); + + for (var i = 0; i < values.Count; i++) + { + var item = values[i]; + + if (i > 0) + { + builder.Append(CacheKeyTokenSeparator); + } + + builder + .Append(item.Key) + .Append(CacheKeyTokenSeparator) + .Append(item.Value); + } + + builder.Append(")"); + } + + private static void CombineCollectionHashCode( + HashCodeCombiner hashCodeCombiner, + string collectionName, + IList> values) + { + if (values != null) + { + hashCodeCombiner.Add(collectionName, StringComparer.Ordinal); + + for (var i = 0; i < values.Count; i++) + { + var item = values[i]; + hashCodeCombiner.Add(item.Key); + hashCodeCombiner.Add(item.Value); + } + } + } + + private static bool AreSame(IList> values1, IList> values2) + { + if (values1 == values2) + { + return true; + } + + if (values1 == null || values2 == null || values1.Count != values2.Count) + { + return false; + } + + for (var i = 0; i < values1.Count; i++) + { + if (!string.Equals(values1[i].Key, values2[i].Key, StringComparison.Ordinal) || + !string.Equals(values1[i].Value, values2[i].Value, StringComparison.Ordinal)) + { + return false; + } + } + + return true; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormatter.cs new file mode 100644 index 0000000000..3e0fbfb317 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormatter.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 System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache +{ + /// + /// Implements by serializing the content + /// in UTF8. + /// + public class DistributedCacheTagHelperFormatter : IDistributedCacheTagHelperFormatter + { + /// + public Task SerializeAsync(DistributedCacheTagHelperFormattingContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.Html == null) + { + throw new ArgumentException( + Resources.FormatPropertyOfTypeCannotBeNull( + nameof(DistributedCacheTagHelperFormattingContext.Html), + typeof(DistributedCacheTagHelperFormattingContext).FullName)); + } + + var serialized = Encoding.UTF8.GetBytes(context.Html.ToString()); + return Task.FromResult(serialized); + } + + /// + public Task DeserializeAsync(byte[] value) + { + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + var content = Encoding.UTF8.GetString(value); + return Task.FromResult(new HtmlString(content)); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormattingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormattingContext.cs new file mode 100644 index 0000000000..3b1de01ff4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormattingContext.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 Microsoft.AspNetCore.Html; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache +{ + /// + /// Represents an object containing the information to serialize with . + /// + public class DistributedCacheTagHelperFormattingContext + { + /// + /// Gets the instance. + /// + public HtmlString Html { get; set; } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperService.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperService.cs new file mode 100644 index 0000000000..34629a2664 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperService.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.Collections.Concurrent; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache +{ + /// + /// Implements and ensures + /// multiple concurrent requests are gated. + /// The entries are stored like this: + /// + /// + /// Int32 representing the hashed cache key size. + /// + /// + /// The UTF8 encoded hashed cache key. + /// + /// + /// The UTF8 encoded cached content. + /// + /// + /// + public class DistributedCacheTagHelperService : IDistributedCacheTagHelperService + { + private readonly IDistributedCacheTagHelperStorage _storage; + private readonly IDistributedCacheTagHelperFormatter _formatter; + private readonly HtmlEncoder _htmlEncoder; + private readonly ILogger _logger; + private readonly ConcurrentDictionary> _workers; + + public DistributedCacheTagHelperService( + IDistributedCacheTagHelperStorage storage, + IDistributedCacheTagHelperFormatter formatter, + HtmlEncoder HtmlEncoder, + ILoggerFactory loggerFactory) + { + if (storage == null) + { + throw new ArgumentNullException(nameof(storage)); + } + + if (formatter == null) + { + throw new ArgumentNullException(nameof(formatter)); + } + + if (HtmlEncoder == null) + { + throw new ArgumentNullException(nameof(HtmlEncoder)); + } + + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _formatter = formatter; + _storage = storage; + _htmlEncoder = HtmlEncoder; + _logger = loggerFactory.CreateLogger(); + _workers = new ConcurrentDictionary>(); + } + + /// + public async Task ProcessContentAsync(TagHelperOutput output, CacheTagKey key, DistributedCacheEntryOptions options) + { + IHtmlContent content = null; + + while (content == null) + { + Task result; + + // Is there any request already processing the value? + if (!_workers.TryGetValue(key, out result)) + { + // There is a small race condition here between TryGetValue and TryAdd that might cause the + // content to be computed more than once. We don't care about this race as the probability of + // happening is very small and the impact is not critical. + var tcs = new TaskCompletionSource(); + + _workers.TryAdd(key, tcs.Task); + + try + { + var serializedKey = Encoding.UTF8.GetBytes(key.GenerateKey()); + var storageKey = key.GenerateHashedKey(); + var value = await _storage.GetAsync(storageKey); + + if (value == null) + { + // The value is not cached, we need to render the tag helper output + var processedContent = await output.GetChildContentAsync(); + + var stringBuilder = new StringBuilder(); + using (var writer = new StringWriter(stringBuilder)) + { + processedContent.WriteTo(writer, _htmlEncoder); + } + + var formattingContext = new DistributedCacheTagHelperFormattingContext + { + Html = new HtmlString(stringBuilder.ToString()) + }; + + // Then cache the result + value = await _formatter.SerializeAsync(formattingContext); + + var encodeValue = Encode(value, serializedKey); + + await _storage.SetAsync(storageKey, encodeValue, options); + + content = formattingContext.Html; + } + else + { + // The value was found in the storage, decode and ensure + // there is no cache key hash collision + byte[] decodedValue = Decode(value, serializedKey); + + try + { + if (decodedValue != null) + { + content = await _formatter.DeserializeAsync(decodedValue); + } + } + catch (Exception e) + { + _logger.DistributedFormatterDeserializationException(storageKey, e); + } + finally + { + // If the deserialization fails the content is rendered + if (content == null) + { + content = await output.GetChildContentAsync(); + } + } + } + } + catch + { + content = null; + throw; + } + finally + { + // Remove the worker task before setting the result. + // If the result is null, other threads would potentially + // acquire it otherwise. + _workers.TryRemove(key, out result); + + // Notify all other awaiters to render the content + tcs.TrySetResult(content); + } + } + else + { + content = await result; + } + } + + return content; + } + + private byte[] Encode(byte[] value, byte[] serializedKey) + { + using (var buffer = new MemoryStream()) + { + var keyLength = BitConverter.GetBytes(serializedKey.Length); + + buffer.Write(keyLength, 0, keyLength.Length); + buffer.Write(serializedKey, 0, serializedKey.Length); + buffer.Write(value, 0, value.Length); + + return buffer.ToArray(); + } + } + + private byte[] Decode(byte[] value, byte[] expectedKey) + { + byte[] decoded = null; + + using (var buffer = new MemoryStream(value)) + { + var keyLengthBuffer = new byte[sizeof(int)]; + buffer.Read(keyLengthBuffer, 0, keyLengthBuffer.Length); + + var keyLength = BitConverter.ToInt32(keyLengthBuffer, 0); + var serializedKeyBuffer = new byte[keyLength]; + buffer.Read(serializedKeyBuffer, 0, serializedKeyBuffer.Length); + + // Ensure we are reading the expected key before continuing + if (serializedKeyBuffer.SequenceEqual(expectedKey)) + { + decoded = new byte[value.Length - keyLengthBuffer.Length - serializedKeyBuffer.Length]; + buffer.Read(decoded, 0, decoded.Length); + } + } + + return decoded; + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperStorage.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperStorage.cs new file mode 100644 index 0000000000..2409f9d5ea --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperStorage.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; +using System.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache +{ + /// + /// Implements by storing the content + /// in using as the store. + /// + public class DistributedCacheTagHelperStorage : IDistributedCacheTagHelperStorage + { + private readonly IDistributedCache _distributedCache; + + /// + /// Creates a new . + /// + /// The to use. + public DistributedCacheTagHelperStorage(IDistributedCache distributedCache) + { + _distributedCache = distributedCache; + } + + /// + public Task GetAsync(string key) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + return _distributedCache.GetAsync(key); + } + + /// + public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options) + { + if (key == null) + { + throw new ArgumentNullException(nameof(key)); + } + + if (value == null) + { + throw new ArgumentNullException(nameof(value)); + } + + return _distributedCache.SetAsync(key, value, options); + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperFormatter.cs new file mode 100644 index 0000000000..6236b7d719 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperFormatter.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.Threading.Tasks; +using Microsoft.AspNetCore.Html; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache +{ + /// + /// An implementation of this interface provides a service to + /// serialize html fragments for being store by + /// + public interface IDistributedCacheTagHelperFormatter + { + /// + /// Serializes some html content. + /// + /// The to serialize. + /// The serialized result. + Task SerializeAsync(DistributedCacheTagHelperFormattingContext context); + + /// + /// Deserialize some html content. + /// + /// The value to deserialize. + /// The deserialized content, null otherwise. + Task DeserializeAsync(byte[] value); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperService.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperService.cs new file mode 100644 index 0000000000..15abaab386 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperService.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.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Caching.Distributed; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache +{ + /// + /// An implementation of this interface provides a service to process + /// the content or fetches it from cache for distributed cache tag helpers. + /// + public interface IDistributedCacheTagHelperService + { + /// + /// Processes the html content of a distributed cache tag helper. + /// + /// The . + /// The key in the storage. + /// The . + /// A cached or new content for the cache tag helper. + Task ProcessContentAsync(TagHelperOutput output, CacheTagKey key, DistributedCacheEntryOptions options); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperStorage.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperStorage.cs new file mode 100644 index 0000000000..2d29c436d7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperStorage.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.Threading.Tasks; +using Microsoft.Extensions.Caching.Distributed; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache +{ + /// + /// An implementation of this interface provides a service to + /// cache distributed html fragments from the <distributed-cache> + /// tag helper. + /// + public interface IDistributedCacheTagHelperStorage + { + /// + /// Gets the content from the cache and deserializes it. + /// + /// The unique key to use in the cache. + /// The stored value if it exists, null otherwise. + Task GetAsync(string key); + + /// + /// Sets the content in the cache and serialized it. + /// + /// The unique key to use in the cache. + /// The value to cache. + /// The cache entry options. + Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options); + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs new file mode 100644 index 0000000000..4a7bb989b7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.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.Diagnostics; +using System.IO; +using System.Text; +using System.Text.Encodings.Web; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.TagHelpers.Cache; +using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers +{ + /// + /// implementation targeting <cache> elements. + /// + public class CacheTagHelper : CacheTagHelperBase + { + /// + /// Prefix used by instances when creating entries in . + /// + public static readonly string CacheKeyPrefix = nameof(CacheTagHelper); + + private const string CachePriorityAttributeName = "priority"; + + // We need to come up with a value for the size of entries when storing a gating Task on the cache. Any value + // greater than 0 will suffice. We choose 56 bytes as an approximation of the size of the task that we store + // in the cache. This size got calculated as an upper bound for the size of an actual task on an x64 architecture + // and corresponds to 24 bytes for the object header block plus the 40 bytes added by the members of the task + // object. + private const int PlaceholderSize = 64; + + /// + /// Creates a new . + /// + /// The factory containing the private instance + /// used by the . + /// The to use. + public CacheTagHelper(CacheTagHelperMemoryCacheFactory factory, HtmlEncoder htmlEncoder) : base(htmlEncoder) + { + MemoryCache = factory.Cache; + } + + /// + /// Gets the instance used to cache entries. + /// + protected IMemoryCache MemoryCache { get; } + + /// + /// Gets or sets the policy for the cache entry. + /// + [HtmlAttributeName(CachePriorityAttributeName)] + public CacheItemPriority? Priority { get; set; } + + /// + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + IHtmlContent content; + if (Enabled) + { + var cacheKey = new CacheTagKey(this, context); + if (MemoryCache.TryGetValue(cacheKey, out Task cachedResult)) + { + // There is either some value already cached (as a Task) or a worker processing the output. + content = await cachedResult; + } + else + { + content = await CreateCacheEntry(cacheKey, output); + } + } + else + { + content = await output.GetChildContentAsync(); + } + + // Clear the contents of the "cache" element since we don't want to render it. + output.SuppressOutput(); + output.Content.SetHtmlContent(content); + } + + private async Task CreateCacheEntry(CacheTagKey cacheKey, TagHelperOutput output) + { + var tokenSource = new CancellationTokenSource(); + + var options = GetMemoryCacheEntryOptions(); + options.AddExpirationToken(new CancellationChangeToken(tokenSource.Token)); + options.SetSize(PlaceholderSize); + var tcs = new TaskCompletionSource(); + + // The returned value is ignored, we only do this so that + // the compiler doesn't complain about the returned task + // not being awaited + _ = MemoryCache.Set(cacheKey, tcs.Task, options); + + IHtmlContent content; + try + { + // The entry is set instead of assigning a value to the + // task so that the expiration options are not impacted + // by the time it took to compute it. + + // Use the CreateEntry to ensure a cache scope is created that will copy expiration tokens from + // cache entries created from the GetChildContentAsync call to the current entry. + var entry = MemoryCache.CreateEntry(cacheKey); + + // The result is processed inside an entry + // such that the tokens are inherited. + + var result = ProcessContentAsync(output); + content = await result; + options.SetSize(GetSize(content)); + entry.SetOptions(options); + + entry.Value = result; + + // An entry gets committed to the cache when disposed gets called. We only want to do this when + // the content has been correctly generated (didn't throw an exception). For that reason the entry + // 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 + // may throw. + tcs.SetResult(content); + return content; + } + catch (Exception ex) + { + // Remove the worker task from the cache in case it can't complete. + tokenSource.Cancel(); + + // Fail the TCS so other awaiters see the exception. + tcs.TrySetException(ex); + throw; + } + finally + { + // The tokenSource needs to be disposed as the MemoryCache + // will register a callback on the Token. + tokenSource.Dispose(); + } + } + + private long GetSize(IHtmlContent content) + { + if (content is CharBufferHtmlContent charBuffer) + { + // We need to multiply the size of the buffer + // by a factor of two due to the fact that + // characters in .NET are UTF-16 which means + // every character uses two bytes (surrogates + // are represented as two characters) + return charBuffer.Buffer.Length * sizeof(char); + } + + Debug.Fail($"{nameof(content)} should be an {nameof(CharBufferHtmlContent)}."); + return -1; + } + + // Internal for unit testing + internal MemoryCacheEntryOptions GetMemoryCacheEntryOptions() + { + var hasEvictionCriteria = false; + var options = new MemoryCacheEntryOptions(); + if (ExpiresOn != null) + { + hasEvictionCriteria = true; + options.SetAbsoluteExpiration(ExpiresOn.Value); + } + + if (ExpiresAfter != null) + { + hasEvictionCriteria = true; + options.SetAbsoluteExpiration(ExpiresAfter.Value); + } + + if (ExpiresSliding != null) + { + hasEvictionCriteria = true; + options.SetSlidingExpiration(ExpiresSliding.Value); + } + + if (Priority != null) + { + options.SetPriority(Priority.Value); + } + + if (!hasEvictionCriteria) + { + options.SetSlidingExpiration(DefaultExpiration); + } + + return options; + } + + private async Task ProcessContentAsync(TagHelperOutput output) + { + var content = await output.GetChildContentAsync(); + + using (var writer = new CharBufferTextWriter()) + { + content.WriteTo(writer, HtmlEncoder); + return new CharBufferHtmlContent(writer.Buffer); + } + } + + private class CharBufferTextWriter : TextWriter + { + public CharBufferTextWriter() + { + Buffer = new PagedCharBuffer(CharArrayBufferSource.Instance); + } + + public override Encoding Encoding => Null.Encoding; + + public PagedCharBuffer Buffer { get; } + + public override void Write(char value) + { + Buffer.Append(value); + } + + public override void Write(char[] buffer, int index, int count) + { + Buffer.Append(buffer, index, count); + } + + public override void Write(string value) + { + Buffer.Append(value); + } + } + + private class CharBufferHtmlContent : IHtmlContent + { + private readonly PagedCharBuffer _buffer; + + public CharBufferHtmlContent(PagedCharBuffer buffer) + { + _buffer = buffer; + } + + public PagedCharBuffer Buffer => _buffer; + + public void WriteTo(TextWriter writer, HtmlEncoder encoder) + { + var length = Buffer.Length; + if (length == 0) + { + return; + } + + for (var i = 0; i < Buffer.Pages.Count; i++) + { + var page = Buffer.Pages[i]; + var pageLength = Math.Min(length, page.Length); + writer.Write(page, index: 0, count: pageLength); + length -= pageLength; + } + + Debug.Assert(length == 0); + } + } + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperBase.cs new file mode 100644 index 0000000000..ca7dfdcb70 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperBase.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; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers +{ + /// + /// base implementation for caching elements. + /// + public abstract class CacheTagHelperBase : TagHelper + { + private const string VaryByAttributeName = "vary-by"; + private const string VaryByHeaderAttributeName = "vary-by-header"; + private const string VaryByQueryAttributeName = "vary-by-query"; + private const string VaryByRouteAttributeName = "vary-by-route"; + private const string VaryByCookieAttributeName = "vary-by-cookie"; + private const string VaryByUserAttributeName = "vary-by-user"; + private const string ExpiresOnAttributeName = "expires-on"; + private const string ExpiresAfterAttributeName = "expires-after"; + private const string ExpiresSlidingAttributeName = "expires-sliding"; + private const string EnabledAttributeName = "enabled"; + + /// + /// The default duration, from the time the cache entry was added, when it should be evicted. + /// This default duration will only be used if no other expiration criteria is specified. + /// The default expiration time is a sliding expiration of 30 seconds. + /// + public static readonly TimeSpan DefaultExpiration = TimeSpan.FromSeconds(30); + + /// + /// Creates a new . + /// + /// The to use. + public CacheTagHelperBase(HtmlEncoder htmlEncoder) + { + HtmlEncoder = htmlEncoder; + } + + /// + public override int Order => -1000; + + /// + /// Gets the which encodes the content to be cached. + /// + protected HtmlEncoder HtmlEncoder { get; } + + /// + /// Gets or sets the for the current executing View. + /// + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + + /// + /// Gets or sets a to vary the cached result by. + /// + [HtmlAttributeName(VaryByAttributeName)] + public string VaryBy { get; set; } + + /// + /// Gets or sets a comma-delimited set of HTTP request headers to vary the cached result by. + /// + [HtmlAttributeName(VaryByHeaderAttributeName)] + public string VaryByHeader { get; set; } + + /// + /// Gets or sets a comma-delimited set of query parameters to vary the cached result by. + /// + [HtmlAttributeName(VaryByQueryAttributeName)] + public string VaryByQuery { get; set; } + + /// + /// Gets or sets a comma-delimited set of route data parameters to vary the cached result by. + /// + [HtmlAttributeName(VaryByRouteAttributeName)] + public string VaryByRoute { get; set; } + + /// + /// Gets or sets a comma-delimited set of cookie names to vary the cached result by. + /// + [HtmlAttributeName(VaryByCookieAttributeName)] + public string VaryByCookie { get; set; } + + /// + /// Gets or sets a value that determines if the cached result is to be varied by the Identity for the logged in + /// . + /// + [HtmlAttributeName(VaryByUserAttributeName)] + public bool VaryByUser { get; set; } + + /// + /// Gets or sets the exact the cache entry should be evicted. + /// + [HtmlAttributeName(ExpiresOnAttributeName)] + public DateTimeOffset? ExpiresOn { get; set; } + + /// + /// Gets or sets the duration, from the time the cache entry was added, when it should be evicted. + /// + [HtmlAttributeName(ExpiresAfterAttributeName)] + public TimeSpan? ExpiresAfter { get; set; } + + /// + /// Gets or sets the duration from last access that the cache entry should be evicted. + /// + [HtmlAttributeName(ExpiresSlidingAttributeName)] + public TimeSpan? ExpiresSliding { get; set; } + + /// + /// Gets or sets the value which determines if the tag helper is enabled or not. + /// + [HtmlAttributeName(EnabledAttributeName)] + public bool Enabled { get; set; } = true; + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperOptions.cs new file mode 100644 index 0000000000..0e141b26ff --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperOptions.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.TagHelpers +{ + /// + /// Provides programmatic configuration for the cache tag helper in the MVC framework. + /// + public class CacheTagHelperOptions + { + /// + /// The maximum total size in bytes that will be cached by the + /// at any given time. + /// + public long SizeLimit { get; set; } = 100 * 1024 * 1024; // 100MB + } +} diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/DependencyInjection/TagHelperExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/DependencyInjection/TagHelperExtensions.cs new file mode 100644 index 0000000000..5161244492 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/DependencyInjection/TagHelperExtensions.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.TagHelpers; +using Microsoft.AspNetCore.Mvc.TagHelpers.Cache; +using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; +using Microsoft.Extensions.DependencyInjection.Extensions; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extension methods for configuring Razor cache tag helpers. + /// + public static class TagHelperServicesExtensions + { + /// + /// Adds MVC cache tag helper services to the application. + /// + /// The . + /// The . + public static IMvcCoreBuilder AddCacheTagHelper(this IMvcCoreBuilder builder) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + builder.Services.TryAddSingleton(); + + // Required default services for cache tag helpers + builder.Services.AddDistributedMemoryCache(); + builder.Services.TryAddSingleton(); + + return builder; + } + + /// + /// Configures the memory size limits on the cache of the . + /// + /// The . + /// The to configure the cache options. + /// The . + public static IMvcBuilder AddCacheTagHelperLimits(this IMvcBuilder builder, Action configure) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + builder.Services.Configure(configure); + + return builder; + } + + /// + /// Configures the memory size limits on the cache of the . + /// + /// The . + /// The to configure the cache options. + /// The . + public static IMvcCoreBuilder AddCacheTagHelperLimits(this IMvcCoreBuilder builder, Action configure) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + builder.Services.Configure(configure); + + return builder; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/DistributedCacheTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/DistributedCacheTagHelper.cs new file mode 100644 index 0000000000..7256f950d9 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/DistributedCacheTagHelper.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 System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.TagHelpers.Cache; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.Memory; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers +{ + /// + /// implementation targeting <distributed-cache> elements. + /// + [HtmlTargetElement("distributed-cache", Attributes = NameAttributeName)] + public class DistributedCacheTagHelper : CacheTagHelperBase + { + private readonly IDistributedCacheTagHelperService _distributedCacheService; + + /// + /// Prefix used by instances when creating entries in . + /// + public static readonly string CacheKeyPrefix = nameof(DistributedCacheTagHelper); + + private const string NameAttributeName = "name"; + + /// + /// Creates a new . + /// + /// The . + /// The . + public DistributedCacheTagHelper( + IDistributedCacheTagHelperService distributedCacheService, + HtmlEncoder htmlEncoder) + : base(htmlEncoder) + { + _distributedCacheService = distributedCacheService; + } + + /// + /// Gets the instance used to cache workers. + /// + protected IMemoryCache MemoryCache { get; } + + /// + /// Gets or sets a unique name to discriminate cached entries. + /// + [HtmlAttributeName(NameAttributeName)] + public string Name { get; set; } + + /// + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + IHtmlContent content; + if (Enabled) + { + var cacheKey = new CacheTagKey(this); + + content = await _distributedCacheService.ProcessContentAsync(output, cacheKey, GetDistributedCacheEntryOptions()); + } + else + { + content = await output.GetChildContentAsync(); + } + + // Clear the contents of the "cache" element since we don't want to render it. + output.SuppressOutput(); + + output.Content.SetHtmlContent(content); + } + + // Internal for unit testing + internal DistributedCacheEntryOptions GetDistributedCacheEntryOptions() + { + var hasEvictionCriteria = false; + var options = new DistributedCacheEntryOptions(); + if (ExpiresOn != null) + { + hasEvictionCriteria = true; + options.SetAbsoluteExpiration(ExpiresOn.Value); + } + + if (ExpiresAfter != null) + { + hasEvictionCriteria = true; + options.SetAbsoluteExpiration(ExpiresAfter.Value); + } + + if (ExpiresSliding != null) + { + hasEvictionCriteria = true; + options.SetSlidingExpiration(ExpiresSliding.Value); + } + + if (!hasEvictionCriteria) + { + options.SetSlidingExpiration(DefaultExpiration); + } + + return options; + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/EnvironmentTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/EnvironmentTagHelper.cs new file mode 100644 index 0000000000..b45a518eb7 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/EnvironmentTagHelper.cs @@ -0,0 +1,156 @@ +// Copyright (c) .NET 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.Hosting; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers +{ + /// + /// implementation targeting <environment> elements that conditionally renders + /// content based on the current value of . + /// If the environment is not listed in the specified or , + /// or if it is in , the content will not be rendered. + /// + public class EnvironmentTagHelper : TagHelper + { + private static readonly char[] NameSeparator = new[] { ',' }; + + /// + /// Creates a new . + /// + /// The . + public EnvironmentTagHelper(IHostingEnvironment hostingEnvironment) + { + HostingEnvironment = hostingEnvironment; + } + + /// + public override int Order => -1000; + + /// + /// A comma separated list of environment names in which the content should be rendered. + /// If the current environment is also in the list, the content will not be rendered. + /// + /// + /// The specified environment names are compared case insensitively to the current value of + /// . + /// + public string Names { get; set; } + + /// + /// A comma separated list of environment names in which the content should be rendered. + /// If the current environment is also in the list, the content will not be rendered. + /// + /// + /// The specified environment names are compared case insensitively to the current value of + /// . + /// + public string Include { get; set; } + + /// + /// A comma separated list of environment names in which the content will not be rendered. + /// + /// + /// The specified environment names are compared case insensitively to the current value of + /// . + /// + public string Exclude { get; set; } + + protected IHostingEnvironment HostingEnvironment { get; } + + /// + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + // Always strip the outer tag name as we never want to render + output.TagName = null; + + if (string.IsNullOrWhiteSpace(Names) && string.IsNullOrWhiteSpace(Include) && string.IsNullOrWhiteSpace(Exclude)) + { + // No names specified, do nothing + return; + } + + var currentEnvironmentName = HostingEnvironment.EnvironmentName?.Trim(); + if (string.IsNullOrEmpty(currentEnvironmentName)) + { + // No current environment name, do nothing + return; + } + + if (Exclude != null) + { + var tokenizer = new StringTokenizer(Exclude, NameSeparator); + foreach (var item in tokenizer) + { + var environment = item.Trim(); + if (environment.HasValue && environment.Length > 0) + { + if (environment.Equals(currentEnvironmentName, StringComparison.OrdinalIgnoreCase)) + { + // Matching environment name found, suppress output + output.SuppressOutput(); + return; + } + } + } + } + + var hasEnvironments = false; + if (Names != null) + { + var tokenizer = new StringTokenizer(Names, NameSeparator); + foreach (var item in tokenizer) + { + var environment = item.Trim(); + if (environment.HasValue && environment.Length > 0) + { + hasEnvironments = true; + if (environment.Equals(currentEnvironmentName, StringComparison.OrdinalIgnoreCase)) + { + // Matching environment name found, do nothing + return; + } + } + } + } + + if (Include != null) + { + var tokenizer = new StringTokenizer(Include, NameSeparator); + foreach (var item in tokenizer) + { + var environment = item.Trim(); + if (environment.HasValue && environment.Length > 0) + { + hasEnvironments = true; + if (environment.Equals(currentEnvironmentName, StringComparison.OrdinalIgnoreCase)) + { + // Matching environment name found, do nothing + return; + } + } + } + } + + if (hasEnvironments) + { + // This instance had at least one non-empty environment (names or include) specified but none of these + // environments matched the current environment. Suppress the output in this case. + output.SuppressOutput(); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs new file mode 100644 index 0000000000..18c89f37e4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs @@ -0,0 +1,271 @@ +// Copyright (c) .NET 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.Rendering; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers +{ + /// + /// implementation targeting <button> elements and <input> elements with + /// their type attribute set to image or submit. + /// + [HtmlTargetElement("button", Attributes = ActionAttributeName)] + [HtmlTargetElement("button", Attributes = ControllerAttributeName)] + [HtmlTargetElement("button", Attributes = AreaAttributeName)] + [HtmlTargetElement("button", Attributes = PageAttributeName)] + [HtmlTargetElement("button", Attributes = PageHandlerAttributeName)] + [HtmlTargetElement("button", Attributes = FragmentAttributeName)] + [HtmlTargetElement("button", Attributes = RouteAttributeName)] + [HtmlTargetElement("button", Attributes = RouteValuesDictionaryName)] + [HtmlTargetElement("button", Attributes = RouteValuesPrefix + "*")] + [HtmlTargetElement("input", Attributes = ImageActionAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = ImageControllerAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = ImageAreaAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = ImagePageAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = ImagePageHandlerAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = ImageFragmentAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = ImageRouteAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = ImageRouteValuesDictionarySelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = ImageRouteValuesSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = SubmitActionAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = SubmitControllerAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = SubmitAreaAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = SubmitPageAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = SubmitPageHandlerAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = SubmitFragmentAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = SubmitRouteAttributeSelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = SubmitRouteValuesDictionarySelector, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("input", Attributes = SubmitRouteValuesSelector, TagStructure = TagStructure.WithoutEndTag)] + public class FormActionTagHelper : TagHelper + { + private const string ActionAttributeName = "asp-action"; + private const string AreaAttributeName = "asp-area"; + private const string ControllerAttributeName = "asp-controller"; + private const string PageAttributeName = "asp-page"; + private const string PageHandlerAttributeName = "asp-page-handler"; + private const string FragmentAttributeName = "asp-fragment"; + private const string RouteAttributeName = "asp-route"; + private const string RouteValuesDictionaryName = "asp-all-route-data"; + private const string RouteValuesPrefix = "asp-route-"; + private const string FormAction = "formaction"; + + private const string ImageTypeSelector = "[type=image], "; + private const string ImageActionAttributeSelector = ImageTypeSelector + ActionAttributeName; + private const string ImageAreaAttributeSelector = ImageTypeSelector + AreaAttributeName; + private const string ImagePageAttributeSelector = ImageTypeSelector + PageAttributeName; + private const string ImagePageHandlerAttributeSelector = ImageTypeSelector + PageHandlerAttributeName; + private const string ImageFragmentAttributeSelector = ImageTypeSelector + FragmentAttributeName; + private const string ImageControllerAttributeSelector = ImageTypeSelector + ControllerAttributeName; + private const string ImageRouteAttributeSelector = ImageTypeSelector + RouteAttributeName; + private const string ImageRouteValuesDictionarySelector = ImageTypeSelector + RouteValuesDictionaryName; + private const string ImageRouteValuesSelector = ImageTypeSelector + RouteValuesPrefix + "*"; + + private const string SubmitTypeSelector = "[type=submit], "; + private const string SubmitActionAttributeSelector = SubmitTypeSelector + ActionAttributeName; + private const string SubmitAreaAttributeSelector = SubmitTypeSelector + AreaAttributeName; + private const string SubmitPageAttributeSelector = SubmitTypeSelector + PageAttributeName; + private const string SubmitPageHandlerAttributeSelector = SubmitTypeSelector + PageHandlerAttributeName; + private const string SubmitFragmentAttributeSelector = SubmitTypeSelector + FragmentAttributeName; + private const string SubmitControllerAttributeSelector = SubmitTypeSelector + ControllerAttributeName; + private const string SubmitRouteAttributeSelector = SubmitTypeSelector + RouteAttributeName; + private const string SubmitRouteValuesDictionarySelector = SubmitTypeSelector + RouteValuesDictionaryName; + private const string SubmitRouteValuesSelector = SubmitTypeSelector + RouteValuesPrefix + "*"; + + private IDictionary _routeValues; + + /// + /// Creates a new . + /// + /// The . + public FormActionTagHelper(IUrlHelperFactory urlHelperFactory) + { + UrlHelperFactory = urlHelperFactory; + } + + /// + public override int Order => -1000; + + /// + /// Gets or sets the for the current request. + /// + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + + protected IUrlHelperFactory UrlHelperFactory { get; } + + /// + /// The name of the action method. + /// + [HtmlAttributeName(ActionAttributeName)] + public string Action { get; set; } + + /// + /// The name of the controller. + /// + [HtmlAttributeName(ControllerAttributeName)] + public string Controller { get; set; } + + /// + /// The name of the area. + /// + [HtmlAttributeName(AreaAttributeName)] + public string Area { get; set; } + + /// + /// The name of the page. + /// + [HtmlAttributeName(PageAttributeName)] + public string Page { get; set; } + + /// + /// The name of the page handler. + /// + [HtmlAttributeName(PageHandlerAttributeName)] + public string PageHandler { get; set; } + + /// + /// Gets or sets the URL fragment. + /// + [HtmlAttributeName(FragmentAttributeName)] + public string Fragment { get; set; } + + /// + /// Name of the route. + /// + /// + /// Must be null if or is non-null. + /// + [HtmlAttributeName(RouteAttributeName)] + public string Route { get; set; } + + /// + /// Additional parameters for the route. + /// + [HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)] + public IDictionary RouteValues + { + get + { + if (_routeValues == null) + { + _routeValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + return _routeValues; + } + set + { + _routeValues = value; + } + } + + /// + /// Does nothing if user provides an formaction attribute. + /// + /// 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 + /// + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + 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 (output.Attributes.ContainsName(FormAction)) + { + if (Action != null || + Controller != null || + Area != null || + Page != null || + PageHandler != null || + Fragment != null || + Route != null || + (_routeValues != null && _routeValues.Count > 0)) + { + // User specified a formaction and one of the bound attributes; can't override that formaction + // attribute. + throw new InvalidOperationException( + Resources.FormatFormActionTagHelper_CannotOverrideFormAction( + FormAction, + output.TagName, + RouteValuesPrefix, + ActionAttributeName, + ControllerAttributeName, + AreaAttributeName, + FragmentAttributeName, + RouteAttributeName, + PageAttributeName, + PageHandlerAttributeName)); + } + + return; + } + + var routeLink = Route != null; + var actionLink = Controller != null || Action != null; + var pageLink = Page != null || PageHandler != null; + + if ((routeLink && actionLink) || (routeLink && pageLink) || (actionLink && pageLink)) + { + var message = string.Join( + Environment.NewLine, + Resources.FormatCannotDetermineAttributeFor(FormAction, '<' + output.TagName + '>'), + RouteAttributeName, + ControllerAttributeName + ", " + ActionAttributeName, + PageAttributeName + ", " + PageHandlerAttributeName); + + throw new InvalidOperationException(message); + } + + RouteValueDictionary routeValues = null; + if (_routeValues != null && _routeValues.Count > 0) + { + routeValues = new RouteValueDictionary(_routeValues); + } + + if (Area != null) + { + if (routeValues == null) + { + routeValues = new RouteValueDictionary(); + } + + // Unconditionally replace any value from asp-route-area. + routeValues["area"] = Area; + } + + var urlHelper = UrlHelperFactory.GetUrlHelper(ViewContext); + string url; + if (pageLink) + { + url = urlHelper.Page(Page, PageHandler, routeValues, protocol: null, host: null, fragment: Fragment); + } + else if (routeLink) + { + url = urlHelper.RouteUrl(Route, routeValues, protocol: null, host: null, fragment: Fragment); + } + else + { + url = urlHelper.Action(Action, Controller, routeValues, protocol: null, host: null, fragment: Fragment); + } + + output.Attributes.SetAttribute(FormAction, url); + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormTagHelper.cs new file mode 100644 index 0000000000..ad10121751 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormTagHelper.cs @@ -0,0 +1,324 @@ +// Copyright (c) .NET 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 Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers +{ + /// + /// implementation targeting <form> elements. + /// + [HtmlTargetElement("form")] + public class FormTagHelper : TagHelper + { + private const string ActionAttributeName = "asp-action"; + private const string AntiforgeryAttributeName = "asp-antiforgery"; + private const string AreaAttributeName = "asp-area"; + private const string PageAttributeName = "asp-page"; + private const string PageHandlerAttributeName = "asp-page-handler"; + private const string FragmentAttributeName = "asp-fragment"; + private const string ControllerAttributeName = "asp-controller"; + private const string RouteAttributeName = "asp-route"; + private const string RouteValuesDictionaryName = "asp-all-route-data"; + private const string RouteValuesPrefix = "asp-route-"; + private const string HtmlActionAttributeName = "action"; + private IDictionary _routeValues; + + /// + /// Creates a new . + /// + /// The . + public FormTagHelper(IHtmlGenerator generator) + { + Generator = generator; + } + + // This TagHelper's order must be lower than the RenderAtEndOfFormTagHelper. I.e it must be executed before + // RenderAtEndOfFormTagHelper does. + /// + public override int Order => -1000; + + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + + protected IHtmlGenerator Generator { get; } + + /// + /// The name of the action method. + /// + [HtmlAttributeName(ActionAttributeName)] + public string Action { get; set; } + + /// + /// The name of the controller. + /// + [HtmlAttributeName(ControllerAttributeName)] + public string Controller { get; set; } + + /// + /// The name of the area. + /// + [HtmlAttributeName(AreaAttributeName)] + public string Area { get; set; } + + /// + /// The name of the page. + /// + [HtmlAttributeName(PageAttributeName)] + public string Page { get; set; } + + /// + /// The name of the page handler. + /// + [HtmlAttributeName(PageHandlerAttributeName)] + public string PageHandler { get; set; } + + /// + /// Whether the antiforgery token should be generated. + /// + /// Defaults to false if user provides an action attribute + /// or if the method is ; true otherwise. + [HtmlAttributeName(AntiforgeryAttributeName)] + public bool? Antiforgery { get; set; } + + /// + /// Gets or sets the URL fragment. + /// + [HtmlAttributeName(FragmentAttributeName)] + public string Fragment { get; set; } + + /// + /// Name of the route. + /// + /// + /// Must be null if or is non-null. + /// + [HtmlAttributeName(RouteAttributeName)] + public string Route { get; set; } + + /// + /// The HTTP method to use. + /// + /// Passed through to the generated HTML in all cases. + [EditorBrowsable(EditorBrowsableState.Never)] + public string Method { get; set; } + + /// + /// Additional parameters for the route. + /// + [HtmlAttributeName(RouteValuesDictionaryName, DictionaryAttributePrefix = RouteValuesPrefix)] + public IDictionary RouteValues + { + get + { + if (_routeValues == null) + { + _routeValues = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + return _routeValues; + } + set + { + _routeValues = value; + } + } + + /// + /// + /// Does nothing if user provides an action attribute and is null or + /// false. + /// + /// + /// Thrown if action attribute is provided and , or are + /// non-null or if the user provided asp-route-* attributes. + /// + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + if (Method != null) + { + output.CopyHtmlAttribute(nameof(Method), context); + } + + var antiforgeryDefault = true; + var routeableParametersProvided = Action != null || + Controller != null || + Area != null || + Page != null || + PageHandler != null || + Fragment != null || + Route != null || + (_routeValues != null && _routeValues.Count > 0); + + // If "action" is already set, it means the user is attempting to use a normal
. + if (output.Attributes.TryGetAttribute(HtmlActionAttributeName, out var actionAttribute)) + { + if (routeableParametersProvided) + { + // User also specified bound attributes we cannot use. + throw new InvalidOperationException( + Resources.FormatFormTagHelper_CannotOverrideAction( + HtmlActionAttributeName, + "", + RouteValuesPrefix, + ActionAttributeName, + ControllerAttributeName, + FragmentAttributeName, + AreaAttributeName, + RouteAttributeName, + PageAttributeName, + PageHandlerAttributeName)); + } + + string attributeValue = null; + switch (actionAttribute.Value) + { + case HtmlString htmlString: + attributeValue = htmlString.ToString(); + break; + case string stringValue: + attributeValue = stringValue; + break; + } + + if (string.IsNullOrEmpty(attributeValue)) + { + // User is using the FormTagHelper like a normal tag that has an empty or complex IHtmlContent action attribute. + // e.g. or + + if (string.Equals(Method ?? "get", "get", StringComparison.OrdinalIgnoreCase)) + { + antiforgeryDefault = false; + } + else + { + // Antiforgery default is already set to true + } + } + else + { + // User is likely using the element to submit to another site. Do not send an antiforgery token to unknown sites. + antiforgeryDefault = false; + } + } + else + { + var routeLink = Route != null; + var actionLink = Controller != null || Action != null; + var pageLink = Page != null || PageHandler != null; + + if ((routeLink && actionLink) || (routeLink && pageLink) || (actionLink && pageLink)) + { + var message = string.Join( + Environment.NewLine, + Resources.FormatCannotDetermineAttributeFor(HtmlActionAttributeName, ""), + RouteAttributeName, + ControllerAttributeName + ", " + ActionAttributeName, + PageAttributeName); + + throw new InvalidOperationException(message); + } + + RouteValueDictionary routeValues = null; + if (_routeValues != null && _routeValues.Count > 0) + { + routeValues = new RouteValueDictionary(_routeValues); + } + + if (Area != null) + { + if (routeValues == null) + { + routeValues = new RouteValueDictionary(); + } + + // Unconditionally replace any value from asp-route-area. + routeValues["area"] = Area; + } + + TagBuilder tagBuilder = null; + if (!routeableParametersProvided && + _routeValues == null && + // Antiforgery will sometime be set globally via TagHelper Initializers, verify it was provided in the cshtml. + !context.AllAttributes.ContainsName(AntiforgeryAttributeName)) + { + // A tag that doesn't utilize asp-* attributes. Let it flow to the output. + Method = Method ?? "get"; + } + else if (pageLink) + { + tagBuilder = Generator.GeneratePageForm( + ViewContext, + Page, + PageHandler, + routeValues, + Fragment, + method: null, + htmlAttributes: null); + } + else if (routeLink) + { + tagBuilder = Generator.GenerateRouteForm( + ViewContext, + Route, + routeValues, + Fragment, + method: null, + htmlAttributes: null); + } + else + { + tagBuilder = Generator.GenerateForm( + ViewContext, + Action, + Controller, + Fragment, + routeValues, + method: null, + htmlAttributes: null); + } + + if (tagBuilder != null) + { + output.MergeAttributes(tagBuilder); + if (tagBuilder.HasInnerHtml) + { + output.PostContent.AppendHtml(tagBuilder.InnerHtml); + } + } + + if (string.Equals(Method, "get", StringComparison.OrdinalIgnoreCase)) + { + antiforgeryDefault = false; + } + } + + if (Antiforgery ?? antiforgeryDefault) + { + var antiforgeryTag = Generator.GenerateAntiforgery(ViewContext); + if (antiforgeryTag != null) + { + output.PostContent.AppendHtml(antiforgeryTag); + } + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs new file mode 100644 index 0000000000..587dfc3567 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs @@ -0,0 +1,115 @@ +// Copyright (c) .NET 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 Microsoft.AspNetCore.Hosting; +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; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers +{ + /// + /// implementation targeting <img> elements that supports file versioning. + /// + /// + /// The tag helper won't process for cases with just the 'src' attribute. + /// + [HtmlTargetElement( + "img", + Attributes = AppendVersionAttributeName + "," + SrcAttributeName, + TagStructure = TagStructure.WithoutEndTag)] + public class ImageTagHelper : UrlResolutionTagHelper + { + private const string AppendVersionAttributeName = "asp-append-version"; + private const string SrcAttributeName = "src"; + + private FileVersionProvider _fileVersionProvider; + + /// + /// Creates a new . + /// + /// The . + /// The . + /// The to use. + /// The . + public ImageTagHelper( + IHostingEnvironment hostingEnvironment, + IMemoryCache cache, + HtmlEncoder htmlEncoder, + IUrlHelperFactory urlHelperFactory) + : base(urlHelperFactory, htmlEncoder) + { + HostingEnvironment = hostingEnvironment; + Cache = cache; + } + + /// + public override int Order => -1000; + + /// + /// Source of the image. + /// + /// + /// Passed through to the generated HTML in all cases. + /// + [HtmlAttributeName(SrcAttributeName)] + public string Src { get; set; } + + /// + /// Value indicating if file version should be appended to the src urls. + /// + /// + /// If true then a query string "v" with the encoded content of the file is added. + /// + [HtmlAttributeName(AppendVersionAttributeName)] + public bool AppendVersion { get; set; } + + protected IHostingEnvironment HostingEnvironment { get; } + + protected IMemoryCache Cache { get; } + + /// + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + output.CopyHtmlAttribute(SrcAttributeName, context); + ProcessUrlAttribute(SrcAttributeName, output); + + if (AppendVersion) + { + EnsureFileVersionProvider(); + + // Retrieve the TagHelperOutput variation of the "src" attribute in case other TagHelpers in the + // pipeline have touched the value. If the value is already encoded this ImageTagHelper may + // not function properly. + Src = output.Attributes[SrcAttributeName].Value as string; + + output.Attributes.SetAttribute(SrcAttributeName, _fileVersionProvider.AddFileVersionToPath(Src)); + } + } + + private void EnsureFileVersionProvider() + { + if (_fileVersionProvider == null) + { + _fileVersionProvider = new FileVersionProvider( + HostingEnvironment.WebRootFileProvider, + Cache, + ViewContext.HttpContext.Request.PathBase); + } + } + } +} \ No newline at end of file diff --git a/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs new file mode 100644 index 0000000000..8ea3effdd4 --- /dev/null +++ b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs @@ -0,0 +1,508 @@ +// Copyright (c) .NET 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.ModelBinding; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; +using Microsoft.AspNetCore.Razor.TagHelpers; + +namespace Microsoft.AspNetCore.Mvc.TagHelpers +{ + /// + /// implementation targeting <input> elements with an asp-for attribute. + /// + [HtmlTargetElement("input", Attributes = ForAttributeName, TagStructure = TagStructure.WithoutEndTag)] + public class InputTagHelper : TagHelper + { + private const string ForAttributeName = "asp-for"; + private const string FormatAttributeName = "asp-format"; + + // Mapping from datatype names and data annotation hints to values for the element's "type" attribute. + private static readonly Dictionary _defaultInputTypes = + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "HiddenInput", InputType.Hidden.ToString().ToLowerInvariant() }, + { "Password", InputType.Password.ToString().ToLowerInvariant() }, + { "Text", InputType.Text.ToString().ToLowerInvariant() }, + { "PhoneNumber", "tel" }, + { "Url", "url" }, + { "EmailAddress", "email" }, + { "Date", "date" }, + { "DateTime", "datetime-local" }, + { "DateTime-local", "datetime-local" }, + { nameof(DateTimeOffset), "text" }, + { "Time", "time" }, + { "Week", "week" }, + { "Month", "month" }, + { nameof(Byte), "number" }, + { nameof(SByte), "number" }, + { nameof(Int16), "number" }, + { nameof(UInt16), "number" }, + { nameof(Int32), "number" }, + { nameof(UInt32), "number" }, + { nameof(Int64), "number" }, + { nameof(UInt64), "number" }, + { nameof(Single), InputType.Text.ToString().ToLowerInvariant() }, + { nameof(Double), InputType.Text.ToString().ToLowerInvariant() }, + { nameof(Boolean), InputType.CheckBox.ToString().ToLowerInvariant() }, + { nameof(Decimal), InputType.Text.ToString().ToLowerInvariant() }, + { nameof(String), InputType.Text.ToString().ToLowerInvariant() }, + { nameof(IFormFile), "file" }, + { TemplateRenderer.IEnumerableOfIFormFileName, "file" }, + }; + + // Mapping from element's type to RFC 3339 date and time formats. + private static readonly Dictionary _rfc3339Formats = + new Dictionary(StringComparer.Ordinal) + { + { "date", "{0:yyyy-MM-dd}" }, + { "datetime", @"{0:yyyy-MM-ddTHH\:mm\:ss.fffK}" }, + { "datetime-local", @"{0:yyyy-MM-ddTHH\:mm\:ss.fff}" }, + { "time", @"{0:HH\:mm\:ss.fff}" }, + }; + + /// + /// Creates a new . + /// + /// The . + public InputTagHelper(IHtmlGenerator generator) + { + Generator = generator; + } + + /// + public override int Order => -1000; + + protected IHtmlGenerator Generator { get; } + + [HtmlAttributeNotBound] + [ViewContext] + public ViewContext ViewContext { get; set; } + + /// + /// An expression to be evaluated against the current model. + /// + [HtmlAttributeName(ForAttributeName)] + public ModelExpression For { get; set; } + + /// + /// The format string (see https://msdn.microsoft.com/en-us/library/txafckwd.aspx) used to format the + /// result. Sets the generated "value" attribute to that formatted string. + /// + /// + /// Not used if the provided (see ) or calculated "type" attribute value is + /// checkbox, password, or radio. That is, is used when calling + /// . + /// + [HtmlAttributeName(FormatAttributeName)] + public string Format { get; set; } + + /// + /// The type of the <input> element. + /// + /// + /// Passed through to the generated HTML in all cases. Also used to determine the + /// helper to call and the default value. A default is not calculated + /// if the provided (see ) or calculated "type" attribute value is checkbox, + /// hidden, password, or radio. + /// + [HtmlAttributeName("type")] + public string InputTypeName { get; set; } + + /// + /// The name of the <input> element. + /// + /// + /// Passed through to the generated HTML in all cases. Also used to determine whether is + /// valid with an empty . + /// + public string Name { get; set; } + + /// + /// The value of the <input> element. + /// + /// + /// Passed through to the generated HTML in all cases. Also used to determine the generated "checked" attribute + /// if is "radio". Must not be null in that case. + /// + public string Value { get; set; } + + /// + /// Does nothing if is null. + /// + /// Thrown if is non-null but is null. + /// + public override void Process(TagHelperContext context, TagHelperOutput output) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (output == null) + { + throw new ArgumentNullException(nameof(output)); + } + + // Pass through attributes that are also well-known HTML attributes. Must be done prior to any copying + // from a TagBuilder. + if (InputTypeName != null) + { + output.CopyHtmlAttribute("type", context); + } + + if (Name != null) + { + output.CopyHtmlAttribute(nameof(Name), context); + } + + if (Value != null) + { + output.CopyHtmlAttribute(nameof(Value), context); + } + + // Note null or empty For.Name is allowed because TemplateInfo.HtmlFieldPrefix may be sufficient. + // IHtmlGenerator will enforce name requirements. + var metadata = For.Metadata; + var modelExplorer = For.ModelExplorer; + if (metadata == null) + { + throw new InvalidOperationException(Resources.FormatTagHelpers_NoProvidedMetadata( + "", + ForAttributeName, + nameof(IModelMetadataProvider), + For.Name)); + } + + string inputType; + string inputTypeHint; + if (string.IsNullOrEmpty(InputTypeName)) + { + // Note GetInputType never returns null. + inputType = GetInputType(modelExplorer, out inputTypeHint); + } + else + { + inputType = InputTypeName.ToLowerInvariant(); + inputTypeHint = null; + } + + // inputType may be more specific than default the generator chooses below. + if (!output.Attributes.ContainsName("type")) + { + output.Attributes.SetAttribute("type", inputType); + } + + // Ensure Generator does not throw due to empty "fullName" if user provided a name attribute. + IDictionary htmlAttributes = null; + if (string.IsNullOrEmpty(For.Name) && + string.IsNullOrEmpty(ViewContext.ViewData.TemplateInfo.HtmlFieldPrefix) && + !string.IsNullOrEmpty(Name)) + { + htmlAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "name", Name }, + }; + } + + TagBuilder tagBuilder; + switch (inputType) + { + case "hidden": + tagBuilder = GenerateHidden(modelExplorer, htmlAttributes); + break; + + case "checkbox": + tagBuilder = GenerateCheckBox(modelExplorer, output, htmlAttributes); + break; + + case "password": + tagBuilder = Generator.GeneratePassword( + ViewContext, + modelExplorer, + For.Name, + value: null, + htmlAttributes: htmlAttributes); + break; + + case "radio": + tagBuilder = GenerateRadio(modelExplorer, htmlAttributes); + break; + + default: + tagBuilder = GenerateTextBox(modelExplorer, inputTypeHint, inputType, htmlAttributes); + break; + } + + if (tagBuilder != null) + { + // This TagBuilder contains the one element of interest. + output.MergeAttributes(tagBuilder); + if (tagBuilder.HasInnerHtml) + { + // Since this is not the "checkbox" special-case, no guarantee that output is a self-closing + // element. A later tag helper targeting this element may change output.TagMode. + output.Content.AppendHtml(tagBuilder.InnerHtml); + } + } + } + + /// + /// Gets an <input> element's "type" attribute value based on the given + /// or . + /// + /// The to use. + /// When this method returns, contains the string, often the name of a + /// base class, used to determine this method's return value. + /// An <input> element's "type" attribute value. + protected string GetInputType(ModelExplorer modelExplorer, out string inputTypeHint) + { + foreach (var hint in GetInputTypeHints(modelExplorer)) + { + if (_defaultInputTypes.TryGetValue(hint, out var inputType)) + { + inputTypeHint = hint; + return inputType; + } + } + + inputTypeHint = InputType.Text.ToString().ToLowerInvariant(); + return inputTypeHint; + } + + private TagBuilder GenerateCheckBox( + ModelExplorer modelExplorer, + TagHelperOutput output, + IDictionary htmlAttributes) + { + if (modelExplorer.ModelType == typeof(string)) + { + if (modelExplorer.Model != null) + { + if (!bool.TryParse(modelExplorer.Model.ToString(), out var potentialBool)) + { + throw new InvalidOperationException(Resources.FormatInputTagHelper_InvalidStringResult( + ForAttributeName, + modelExplorer.Model.ToString(), + typeof(bool).FullName)); + } + } + } + else if (modelExplorer.ModelType != typeof(bool)) + { + throw new InvalidOperationException(Resources.FormatInputTagHelper_InvalidExpressionResult( + "", + ForAttributeName, + modelExplorer.ModelType.FullName, + typeof(bool).FullName, + typeof(string).FullName, + "type", + "checkbox")); + } + + // hiddenForCheckboxTag always rendered after the returned element + var hiddenForCheckboxTag = Generator.GenerateHiddenForCheckbox(ViewContext, modelExplorer, For.Name); + if (hiddenForCheckboxTag != null) + { + var renderingMode = + output.TagMode == TagMode.SelfClosing ? TagRenderMode.SelfClosing : TagRenderMode.StartTag; + hiddenForCheckboxTag.TagRenderMode = renderingMode; + if (!hiddenForCheckboxTag.Attributes.ContainsKey("name") && + !string.IsNullOrEmpty(Name)) + { + // The checkbox and hidden elements should have the same name attribute value. Attributes will + // match if both are present because both have a generated value. Reach here in the special case + // where user provided a non-empty fallback name. + hiddenForCheckboxTag.MergeAttribute("name", Name); + } + + if (ViewContext.FormContext.CanRenderAtEndOfForm) + { + ViewContext.FormContext.EndOfFormContent.Add(hiddenForCheckboxTag); + } + else + { + output.PostElement.AppendHtml(hiddenForCheckboxTag); + } + } + + return Generator.GenerateCheckBox( + ViewContext, + modelExplorer, + For.Name, + isChecked: null, + htmlAttributes: htmlAttributes); + } + + private TagBuilder GenerateRadio(ModelExplorer modelExplorer, IDictionary htmlAttributes) + { + // Note empty string is allowed. + if (Value == null) + { + throw new InvalidOperationException(Resources.FormatInputTagHelper_ValueRequired( + "", + nameof(Value).ToLowerInvariant(), + "type", + "radio")); + } + + return Generator.GenerateRadioButton( + ViewContext, + modelExplorer, + For.Name, + Value, + isChecked: null, + htmlAttributes: htmlAttributes); + } + + private TagBuilder GenerateTextBox( + ModelExplorer modelExplorer, + string inputTypeHint, + string inputType, + IDictionary htmlAttributes) + { + var format = Format; + if (string.IsNullOrEmpty(format)) + { + if (!modelExplorer.Metadata.HasNonDefaultEditFormat && + string.Equals("week", inputType, StringComparison.OrdinalIgnoreCase) && + (modelExplorer.Model is DateTime || modelExplorer.Model is DateTimeOffset)) + { + modelExplorer = modelExplorer.GetExplorerForModel(FormatWeekHelper.GetFormattedWeek(modelExplorer)); + } + else + { + format = GetFormat(modelExplorer, inputTypeHint, inputType); + } + } + + if (htmlAttributes == null) + { + htmlAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + htmlAttributes["type"] = inputType; + if (string.Equals(inputType, "file") && + string.Equals( + inputTypeHint, + TemplateRenderer.IEnumerableOfIFormFileName, + StringComparison.OrdinalIgnoreCase)) + { + htmlAttributes["multiple"] = "multiple"; + } + + return Generator.GenerateTextBox( + ViewContext, + modelExplorer, + For.Name, + modelExplorer.Model, + format, + htmlAttributes); + } + + // Imitate Generator.GenerateHidden() using Generator.GenerateTextBox(). This adds support for asp-format that + // is not available in Generator.GenerateHidden(). + private TagBuilder GenerateHidden(ModelExplorer modelExplorer, IDictionary htmlAttributes) + { + var value = For.Model; + if (value is byte[] byteArrayValue) + { + value = Convert.ToBase64String(byteArrayValue); + } + + if (htmlAttributes == null) + { + htmlAttributes = new Dictionary(StringComparer.OrdinalIgnoreCase); + } + + // In DefaultHtmlGenerator(), GenerateTextBox() calls GenerateInput() _almost_ identically to how + // GenerateHidden() does and the main switch inside GenerateInput() handles InputType.Text and + // InputType.Hidden identically. No behavior differences at all when a type HTML attribute already exists. + htmlAttributes["type"] = "hidden"; + + return Generator.GenerateTextBox(ViewContext, modelExplorer, For.Name, value, Format, htmlAttributes); + } + + // Get a fall-back format based on the metadata. + private string GetFormat(ModelExplorer modelExplorer, string inputTypeHint, string inputType) + { + string format; + if (string.Equals("month", inputType, StringComparison.OrdinalIgnoreCase)) + { + // "month" is a new HTML5 input type that only will be rendered in Rfc3339 mode + format = "{0:yyyy-MM}"; + } + else if (string.Equals("decimal", inputTypeHint, StringComparison.OrdinalIgnoreCase) && + string.Equals("text", inputType, StringComparison.Ordinal) && + string.IsNullOrEmpty(modelExplorer.Metadata.EditFormatString)) + { + // Decimal data is edited using an element, with a reasonable format. + // EditFormatString has precedence over this fall-back format. + format = "{0:0.00}"; + } + else if (ViewContext.Html5DateRenderingMode == Html5DateRenderingMode.Rfc3339 && + !modelExplorer.Metadata.HasNonDefaultEditFormat && + (typeof(DateTime) == modelExplorer.Metadata.UnderlyingOrModelType || + typeof(DateTimeOffset) == modelExplorer.Metadata.UnderlyingOrModelType)) + { + // Rfc3339 mode _may_ override EditFormatString in a limited number of cases. Happens only when + // EditFormatString has a default format i.e. came from a [DataType] attribute. + if (string.Equals("text", inputType) && + string.Equals(nameof(DateTimeOffset), inputTypeHint, StringComparison.OrdinalIgnoreCase)) + { + // Auto-select a format that round-trips Offset and sub-Second values in a DateTimeOffset. Not + // done if user chose the "text" type in .cshtml file or with data annotations i.e. when + // inputTypeHint==null or "text". + format = _rfc3339Formats["datetime"]; + } + else if (_rfc3339Formats.TryGetValue(inputType, out var rfc3339Format)) + { + format = rfc3339Format; + } + else + { + // Otherwise use default EditFormatString. + format = modelExplorer.Metadata.EditFormatString; + } + } + else + { + // Otherwise use EditFormatString, if any. + format = modelExplorer.Metadata.EditFormatString; + } + + return format; + } + + // A variant of TemplateRenderer.GetViewNames(). Main change relates to bool? handling. + private static IEnumerable GetInputTypeHints(ModelExplorer modelExplorer) + { + if (!string.IsNullOrEmpty(modelExplorer.Metadata.TemplateHint)) + { + yield return modelExplorer.Metadata.TemplateHint; + } + + if (!string.IsNullOrEmpty(modelExplorer.Metadata.DataTypeName)) + { + yield return modelExplorer.Metadata.DataTypeName; + } + + // In most cases, we don't want to search for Nullable. We want to search for T, which should handle + // both T and Nullable. However we special-case bool? to avoid turning an into a element the SelectTagHelper targeted? + object formDataEntry; + context.Items.TryGetValue(typeof(SelectTagHelper), out formDataEntry); + + // ... And did the SelectTagHelper determine any selected values? + var currentValues = formDataEntry as CurrentValues; + if (currentValues?.Values != null && currentValues.Values.Count != 0) + { + // Select this