Merge branch 'release' of github.com:aspnet/Mvc into release

This commit is contained in:
Wei Wang 2015-01-28 18:23:42 -08:00
commit 58b5445215
919 changed files with 25590 additions and 6356 deletions

View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22013.1
VisualStudioVersion = 14.0.22416.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@ -33,10 +33,6 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Razor.
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Test", "test\Microsoft.AspNet.Mvc.Test\Microsoft.AspNet.Mvc.Test.kproj", "{5F945B82-FE5F-425C-956C-8BC2F2020254}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.HeaderValueAbstractions", "src\Microsoft.AspNet.Mvc.HeaderValueAbstractions\Microsoft.AspNet.Mvc.HeaderValueAbstractions.kproj", "{98335B23-E4B9-4CAD-9749-0DED32A659A1}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.HeaderValueAbstractions.Tests", "test\Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test\Microsoft.AspNet.Mvc.HeaderValueAbstractions.Tests.kproj", "{E69FD235-2042-43A4-9970-59CB29955B4E}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FAD65E9C-3CF3-4F68-9757-C7358604030B}"
ProjectSection(SolutionItems) = preProject
global.json = global.json
@ -46,6 +42,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.WebApi
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.WebApiCompatShimTest", "test\Microsoft.AspNet.Mvc.WebApiCompatShimTest\Microsoft.AspNet.Mvc.WebApiCompatShimTest.kproj", "{5DE8E4D9-AACD-4B5F-819F-F091383FB996}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.TestConfiguration", "test\WebSites\Microsoft.AspNet.Mvc.TestConfiguration\Microsoft.AspNet.Mvc.TestConfiguration.kproj", "{680D75ED-601F-4D86-B01B-1072D0C31B8C}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -176,26 +174,6 @@ Global
{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
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|x86.ActiveCfg = Debug|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Any CPU.Build.0 = Release|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|x86.ActiveCfg = Release|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|x86.ActiveCfg = Debug|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Any CPU.Build.0 = Release|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.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
@ -216,6 +194,18 @@ Global
{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
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|x86.ActiveCfg = Debug|Any CPU
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|x86.Build.0 = Debug|Any CPU
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|Any CPU.Build.0 = Release|Any CPU
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|x86.ActiveCfg = Release|Any CPU
{680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -233,9 +223,8 @@ Global
{520B3AA4-363A-497C-8C15-80423C5AFC85} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{7C4F5973-0491-4028-B1DC-A9BA73FF9F77} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{5F945B82-FE5F-425C-956C-8BC2F2020254} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{98335B23-E4B9-4CAD-9749-0DED32A659A1} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{E69FD235-2042-43A4-9970-59CB29955B4E} = {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}
{680D75ED-601F-4D86-B01B-1072D0C31B8C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
EndGlobalSection
EndGlobal

219
Mvc.sln
View File

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.22213.0
VisualStudioVersion = 14.0.22512.0
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject
@ -49,18 +49,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RoutingWebSite", "test\WebS
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Test", "test\Microsoft.AspNet.Mvc.Test\Microsoft.AspNet.Mvc.Test.kproj", "{5F945B82-FE5F-425C-956C-8BC2F2020254}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CompositeViewEngine", "test\WebSites\CompositeViewEngine\CompositeViewEngine.kproj", "{A853B2BA-4449-4908-A416-5A3C027FC22B}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorWebSite", "test\WebSites\RazorWebSite\RazorWebSite.kproj", "{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FormatterWebSite", "test\WebSites\FormatterWebSite\FormatterWebSite.kproj", "{62735776-46FF-4170-9392-02E128A69B89}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ValueProvidersSite", "test\WebSites\ValueProvidersSite\ValueProvidersSite.kproj", "{14F79E79-AE79-48FA-95DE-D794EF4EABB3}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.HeaderValueAbstractions", "src\Microsoft.AspNet.Mvc.HeaderValueAbstractions\Microsoft.AspNet.Mvc.HeaderValueAbstractions.kproj", "{98335B23-E4B9-4CAD-9749-0DED32A659A1}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.HeaderValueAbstractions.Tests", "test\Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test\Microsoft.AspNet.Mvc.HeaderValueAbstractions.Tests.kproj", "{E69FD235-2042-43A4-9970-59CB29955B4E}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ModelBindingWebSite", "test\WebSites\ModelBindingWebSite\ModelBindingWebSite.kproj", "{EE1AB716-F102-4CA3-AD2C-214A44B459A0}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FAD65E9C-3CF3-4F68-9757-C7358604030B}"
@ -110,6 +102,28 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PrecompilationWebSite", "te
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RequestServicesWebSite", "test\WebSites\RequestServicesWebSite\RequestServicesWebSite.kproj", "{F12E9CF0-4958-40C6-A6CD-759185157F84}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorViewEngineOptionsWebsite", "test\WebSites\RazorViewEngineOptionsWebsite\RazorViewEngineOptionsWebsite.kproj", "{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CompositeViewEngineWebSite", "test\WebSites\CompositeViewEngineWebSite\CompositeViewEngineWebSite.kproj", "{A853B2BA-4449-4908-A416-5A3C027FC22B}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ValueProvidersWebSite", "test\WebSites\ValueProvidersWebSite\ValueProvidersWebSite.kproj", "{14F79E79-AE79-48FA-95DE-D794EF4EABB3}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MvcTagHelpersWebSite", "test\WebSites\MvcTagHelpersWebSite\MvcTagHelpersWebSite.kproj", "{920F8A0E-6F7D-4BBE-84FF-840B89099BE6}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ActionResultsWebSite", "test\WebSites\ActionResultsWebSite\ActionResultsWebSite.kproj", "{0A6BB4C0-48D3-4E7F-952B-B8917345E075}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "LoggingWebSite", "test\WebSites\LoggingWebSite\LoggingWebSite.kproj", "{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ErrorPageMiddlewareWebSite", "test\WebSites\ErrorPageMiddlewareWebSite\ErrorPageMiddlewareWebSite.kproj", "{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ActionConstraintsWebSite", "test\WebSites\ActionConstraintsWebSite\ActionConstraintsWebSite.kproj", "{AF210F69-9D31-43AF-AC3A-CD366E252218}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CustomRouteWebSite", "test\WebSites\CustomRouteWebSite\CustomRouteWebSite.kproj", "{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResponseCacheWebSite", "test\WebSites\ResponseCacheWebSite\ResponseCacheWebSite.kproj", "{BDEEBE09-C0C4-433C-B0B8-8478C9776996}"
EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Common.Test", "test\Microsoft.AspNet.Mvc.Common.Test\Microsoft.AspNet.Mvc.Common.Test.kproj", "{0449D6D2-BE1B-4E29-8E1B-444420802C03}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -310,16 +324,6 @@ Global
{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
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|x86.ActiveCfg = Debug|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Any CPU.Build.0 = Release|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.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
@ -340,36 +344,6 @@ Global
{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
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|x86.ActiveCfg = Debug|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Any CPU.Build.0 = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|x86.ActiveCfg = Release|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Any CPU.Build.0 = Debug|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|x86.ActiveCfg = Debug|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Any CPU.ActiveCfg = Release|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Any CPU.Build.0 = Release|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{98335B23-E4B9-4CAD-9749-0DED32A659A1}.Release|x86.ActiveCfg = Release|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Debug|x86.ActiveCfg = Debug|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Any CPU.Build.0 = Release|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{E69FD235-2042-43A4-9970-59CB29955B4E}.Release|x86.ActiveCfg = Release|Any CPU
{EE1AB716-F102-4CA3-AD2C-214A44B459A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE1AB716-F102-4CA3-AD2C-214A44B459A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE1AB716-F102-4CA3-AD2C-214A44B459A0}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
@ -590,6 +564,138 @@ Global
{F12E9CF0-4958-40C6-A6CD-759185157F84}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{F12E9CF0-4958-40C6-A6CD-759185157F84}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{F12E9CF0-4958-40C6-A6CD-759185157F84}.Release|x86.ActiveCfg = Release|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|x86.ActiveCfg = Debug|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|x86.Build.0 = Debug|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|Any CPU.Build.0 = Release|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|x86.ActiveCfg = Release|Any CPU
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|x86.Build.0 = Release|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|x86.ActiveCfg = Debug|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|x86.Build.0 = Debug|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Any CPU.Build.0 = Release|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|x86.ActiveCfg = Release|Any CPU
{A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|x86.Build.0 = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|x86.ActiveCfg = Debug|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|x86.Build.0 = Debug|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Any CPU.Build.0 = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|x86.ActiveCfg = Release|Any CPU
{14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|x86.Build.0 = 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
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|x86.ActiveCfg = Debug|Any CPU
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Debug|x86.Build.0 = Debug|Any CPU
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|Any CPU.Build.0 = Release|Any CPU
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|x86.ActiveCfg = Release|Any CPU
{0A6BB4C0-48D3-4E7F-952B-B8917345E075}.Release|x86.Build.0 = Release|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.Debug|x86.ActiveCfg = Debug|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.Debug|x86.Build.0 = Debug|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.Release|Any CPU.Build.0 = Release|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.Release|x86.ActiveCfg = Release|Any CPU
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E}.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
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|x86.ActiveCfg = Debug|Any CPU
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Debug|x86.Build.0 = Debug|Any CPU
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|Any CPU.Build.0 = Release|Any CPU
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|x86.ActiveCfg = Release|Any CPU
{AF210F69-9D31-43AF-AC3A-CD366E252218}.Release|x86.Build.0 = Release|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|x86.ActiveCfg = Debug|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Debug|x86.Build.0 = Debug|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|Any CPU.Build.0 = Release|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|x86.ActiveCfg = Release|Any CPU
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B}.Release|x86.Build.0 = Release|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Debug|x86.ActiveCfg = Debug|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Debug|x86.Build.0 = Debug|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|Any CPU.Build.0 = Release|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|x86.ActiveCfg = Release|Any CPU
{BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|x86.Build.0 = Release|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|x86.ActiveCfg = Debug|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|x86.Build.0 = Debug|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|Any CPU.Build.0 = Release|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|x86.ActiveCfg = Release|Any CPU
{0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -615,12 +721,8 @@ Global
{680D75ED-601F-4D86-B01B-1072D0C31B8C} = {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}
{A853B2BA-4449-4908-A416-5A3C027FC22B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{62735776-46FF-4170-9392-02E128A69B89} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{14F79E79-AE79-48FA-95DE-D794EF4EABB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{98335B23-E4B9-4CAD-9749-0DED32A659A1} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E}
{E69FD235-2042-43A4-9970-59CB29955B4E} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{EE1AB716-F102-4CA3-AD2C-214A44B459A0} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{C6E5AFFA-890A-448F-8DE3-878B1D3C9FC7} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{A353B17E-A940-4CE8-8BF9-179E24A9041F} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
@ -643,5 +745,16 @@ Global
{860119ED-3DB1-424D-8D0A-30132A8A7D96} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{59E1BE90-92C1-4D35-ADCC-B69F49077C81} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{F12E9CF0-4958-40C6-A6CD-759185157F84} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{A853B2BA-4449-4908-A416-5A3C027FC22B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{14F79E79-AE79-48FA-95DE-D794EF4EABB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{920F8A0E-6F7D-4BBE-84FF-840B89099BE6} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{0A6BB4C0-48D3-4E7F-952B-B8917345E075} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{0AD78AB5-D67C-49BC-81B1-0C51BFA82B5E} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{AD545A5B-2BA5-4314-88AC-FC2ACF2CC718} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{AF210F69-9D31-43AF-AC3A-CD366E252218} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{BDEEBE09-C0C4-433C-B0B8-8478C9776996} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{0449D6D2-BE1B-4E29-8E1B-444420802C03} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
EndGlobalSection
EndGlobal

View File

@ -12,6 +12,11 @@
<Analyzers>
<Analyzer AnalyzerId="StyleCop.CSharp.NamingRules">
<Rules>
<Rule Name="ConstFieldNamesMustBeginWithUpperCaseLetter">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
<Rule Name="FieldNamesMustNotBeginWithUnderscore">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
@ -22,6 +27,11 @@
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
<Rule Name="StaticReadonlyFieldsMustBeginWithUpperCaseLetter">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
</Rules>
<AnalyzerSettings>
<CollectionProperty Name="Hungarian">
@ -274,6 +284,11 @@
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
<Rule Name="PrefixCallsCorrectly">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
<Rule Name="PrefixLocalCallsWithThis">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
@ -384,11 +399,21 @@
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
<Rule Name="InstanceReadonlyElementsMustAppearBeforeInstanceNonReadonlyElements">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
<Rule Name="StaticElementsMustAppearBeforeInstanceElements">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
<Rule Name="StaticReadonlyElementsMustAppearBeforeStaticNonReadonlyElements">
<RuleSettings>
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
</Rules>
<AnalyzerSettings />
</Analyzer>
@ -399,7 +424,7 @@
<BooleanProperty Name="Enabled">False</BooleanProperty>
</RuleSettings>
</Rule>
<!-- Creates a lot of noise with anonymous objects -->
<Rule Name="OpeningCurlyBracketsMustBeSpacedCorrectly">
<RuleSettings>

View File

@ -19,10 +19,10 @@ IF EXIST packages\KoreBuild goto run
.nuget\NuGet.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre
.nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion
IF "%SKIP_KRE_INSTALL%"=="1" goto run
CALL packages\KoreBuild\build\kvm upgrade -runtime CLR -x86
CALL packages\KoreBuild\build\kvm install default -runtime CoreCLR -x86
IF "%SKIP_DOTNET_INSTALL%"=="1" goto run
CALL packages\KoreBuild\build\dotnetsdk upgrade -runtime CLR -x86
CALL packages\KoreBuild\build\dotnetsdk install default -runtime CoreCLR -x86
:run
CALL packages\KoreBuild\build\kvm use default -runtime CLR -x86
CALL packages\KoreBuild\build\dotnetsdk use default -runtime CLR -x86
packages\Sake\tools\Sake.exe -I packages\KoreBuild\build -f makefile.shade %*

View File

@ -28,11 +28,11 @@ if test ! -d packages/KoreBuild; then
fi
if ! type k > /dev/null 2>&1; then
source packages/KoreBuild/build/kvm.sh
source packages/KoreBuild/build/dotnetsdk.sh
fi
if ! type k > /dev/null 2>&1; then
kvm upgrade
dotnetsdk upgrade
fi
mono packages/Sake/tools/Sake.exe -I packages/KoreBuild/build -f makefile.shade "$@"

View File

@ -12,13 +12,17 @@ namespace MvcSample.Web.ApiExplorerSamples
public class ProductsAdminController : Controller
{
[HttpPut]
public void AddProduct([FromBody] Product product)
[Produces("application/json", Type = typeof(Product))]
public IActionResult AddProduct([FromBody] Product product)
{
return null;
}
[HttpPost("{id}")]
public void UpdateProduct([FromBody] Product product)
[HttpPost("{id?}")]
[Produces("application/json", Type = typeof(Product))]
public IActionResult UpdateProduct(UpdateProductDTO dto)
{
return null;
}
[HttpPost("{id}/Stock")]

View File

@ -29,5 +29,12 @@ namespace MvcSample.Web.ApiExplorerSamples
{
return null;
}
[Produces("application/json", Type = typeof(ProductOrderConfirmation))]
[HttpPut("{order.acountId:int}/PlaceOrder")]
public IActionResult PlaceOrder(Order order)
{
return null;
}
}
}

View File

@ -50,14 +50,14 @@ namespace MvcSample.Web
return new ChallengeResult();
}
[Authorize("Permission", "CanViewPage")]
[Authorize("CanViewPage")]
public ActionResult NotGrantedClaim(int age = 20, string userName = "SampleUser")
{
return Index(age, userName);
}
[FakeUser]
[Authorize("Permission", "CanViewPage", "CanViewAnything")]
[Authorize("CanViewAnything")]
public ActionResult AllGranted(int age = 20, string userName = "SampleUser")
{
return Index(age, userName);

View File

@ -18,8 +18,6 @@ namespace MvcSample.Web.RandomNameSpace
get; set;
}
public ActionContext ActionContext { get; set; }
public string Index()
{
return "Hello World: my namespace is " + this.GetType().Namespace;

View File

@ -2,7 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering;
@ -89,6 +92,26 @@ namespace MvcSample.Web
return View("MyView", user);
}
[Activate]
public IHostingEnvironment HostingEnvironment { get; set; }
/// <summary>
/// Action that shows multiple file upload.
/// </summary>
public async Task<ActionResult> PostFile(IList<IFormFile> files)
{
if (!ModelState.IsValid)
{
return View("MyView");
}
foreach (var f in files)
{
await f.SaveAsAsync(Path.Combine(HostingEnvironment.WebRoot, "test-file" + files.IndexOf(f)));
}
return View();
}
/// <summary>
/// Action that exercises input formatter
/// </summary>

View File

@ -0,0 +1,27 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
using System.Collections.Generic;
namespace MvcSample.Web.ApiExplorerSamples
{
public class Order
{
[FromRoute]
public int AccountId { get; set; }
[FromBody]
public List<OrderItem> Items { get; set; }
[FromQuery]
public bool? IncludeWarranty { get; set; }
public class OrderItem
{
public int ProductId { get; set; }
public int Quantity { get; set; }
}
}
}

View File

@ -5,6 +5,8 @@ namespace MvcSample.Web.ApiExplorerSamples
{
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }

View File

@ -0,0 +1,14 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace MvcSample.Web.ApiExplorerSamples
{
public class ProductChangeDTO
{
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
namespace MvcSample.Web.ApiExplorerSamples
{
public class UpdateProductDTO
{
public int Id { get; set; }
[FromBody]
public Product Product { get; set; }
[FromHeader(Name = "Admin-User")]
public string AdminId { get; set; }
public string Comments { get; set; }
}
}

View File

@ -2,11 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Security.Claims;
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Security;
using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.DependencyInjection;
using MvcSample.Web.Filters;
@ -25,10 +25,10 @@ namespace MvcSample.Web
{
app.UseFileServer();
#if ASPNET50
// We use Path.Combine here so that it works on platforms other than Windows as well.
// Set up configuration sources.
var configuration = new Configuration()
.AddJsonFile(Path.Combine("App_Data", "config.json"))
.AddEnvironmentVariables();
.AddJsonFile("config.json")
.AddEnvironmentVariables();
string diSystem;
if (configuration.TryGet("DependencyInjection", out diSystem) &&
@ -38,6 +38,19 @@ namespace MvcSample.Web
app.UseServices(services =>
{
services.ConfigureAuthorization(auth =>
{
auth.AddPolicy("CanViewPage",
new AuthorizationPolicyBuilder()
.RequiresClaim("Permission", "CanViewPage", "CanViewAnything").Build());
auth.AddPolicy("CanViewAnything",
new AuthorizationPolicyBuilder()
.RequiresClaim("Permission", "CanViewAnything").Build());
// This policy basically requires that the auth type is present
var basicPolicy = new AuthorizationPolicyBuilder("Basic").RequiresClaim(ClaimTypes.NameIdentifier);
auth.AddPolicy("RequireBasic", basicPolicy.Build());
});
services.AddMvc();
services.AddSingleton<PassThroughAttribute>();
services.AddSingleton<UserNameService>();
@ -50,6 +63,8 @@ namespace MvcSample.Web
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(typeof(PassThroughAttribute), order: 17);
options.AddXmlDataContractSerializerFormatter();
});
services.Configure<RazorViewEngineOptions>(options =>
{
@ -90,6 +105,7 @@ namespace MvcSample.Web
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(typeof(PassThroughAttribute), order: 17);
options.AddXmlDataContractSerializerFormatter();
});
});
}

View File

@ -2,38 +2,58 @@
@model ApiDescription
<div class="api-description">
<h4>@(Model.HttpMethod ?? "*") - @(Model.RelativePath ?? "Unknown Url")</h4>
<h4>
<span title="@Model.ActionDescriptor.DisplayName">
@(Model.HttpMethod ?? "*") - @(Model.RelativePath ?? "Unknown Url")
</span>
</h4>
<hr />
<h5>For action: @Model.ActionDescriptor.DisplayName</h5>
<p>Return Type: @(Model.ResponseType?.FullName ?? "Unknown Type")</p>
<h5>Parameters:</h5>
@if (Model.ParameterDescriptions.Count > 0)
{
<p>Parameters:</p>
<ul>
@foreach (var parameter in Model.ParameterDescriptions)
{
<li>
@parameter.Name - @(parameter?.Type?.FullName ?? "Unknown") - @parameter.Source.ToString()
@if (parameter.Constraints != null && parameter.Constraints.Any())
{
var constraints = parameter.Constraints;
Write("-Constraint:" + string.Join(", ", constraints.Select(c => c.GetType()?.Name?.Replace("RouteConstraint", ""))));
}
- Default value: @(parameter?.DefaultValue ?? " none")
</li>
}
</ul>
<table>
<thead>
<tr>
<td>Name</td>
<td>Data Type</td>
<td>Source</td>
</tr>
</thead>
<tbody>
@foreach (var parameter in Model.ParameterDescriptions)
{
<tr>
<td>@parameter.Name</td>
<td>@(parameter.Type?.FullName ?? "Unknown Type")</td>
<td>@parameter.Source.Id</td>
</tr>
}
</tbody>
</table>
}
<h5>Response Formats</h5>
@if (Model.SupportedResponseFormats.Count > 0)
{
<p>Response Formats:</p>
<ul>
@foreach (var response in Model.SupportedResponseFormats)
{
<li>@response.MediaType.RawValue - @response.Formatter.GetType().Name</li>
}
</ul>
<table>
<thead>
<tr>
<td>Data Type</td>
<td>Media Type</td>
<td>Formatter</td>
</tr>
</thead>
<tbody>
@foreach (var response in Model.SupportedResponseFormats)
{
<tr>
<td>@Model.ResponseType.FullName</td>
<td>@response.MediaType.ToString()</td>
<td>@response.Formatter.GetType().Name</td>
</tr>
}
</tbody>
</table>
}
</div>

View File

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>File Upload Successful</title>
</head>
<body>
<h2>File upload successful.</h2>
</body>
</html>

View File

@ -51,6 +51,13 @@
<h1>ASP.NET</h1>
<p class="lead">ASP.NET is a free web framework for building great Web sites and Web applications using HTML, CSS and JavaScript.</p>
<p><a href="http://asp.net" class="btn btn-primary btn-large">Learn more &raquo;</a></p>
File Upload Demo: <br/>
<form action="Home/PostFile" method="post" enctype="multipart/form-data">
<input type="file" name="files" id="file1" />
<input type="file" name="files" id="file2" />
<input type="submit" value="submit" />
</form>
</div>
<div class="row">
<h3 title="@(Model?.Name)" class="@nullValue">Hello @Html.DisplayTextFor(User => User)! Happy @(Model?.Age) birthday.</h3>

View File

@ -1,4 +1,8 @@
{
"commands": {
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
},
"compilationOptions": {
"warningsAsErrors": true
},
@ -8,11 +12,7 @@
"Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.AspNet.StaticFiles": "1.0.0-*",
},
"commands": {
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
},
"frameworks": {
"aspnet50": {

View File

@ -3,8 +3,30 @@
padding: 5px;
}
.api-description h5 {
margin-top: 20px;
}
.api-description hr {
border: 0px;
border-top: 1px solid #D44B4B;
margin: 0px;
margin-bottom: 10px;
}
.api-description table {
width: 100%;
}
.api-description thead {
border-bottom: 1px solid black;
}
.api-description td {
border-bottom: 1px solid #CCCCCC;
color: #666666;
padding: 5px;
}
.api-description tr:last-child td {
border-bottom: none;
}

View File

@ -0,0 +1,44 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc;
using Microsoft.Framework.Cache.Memory;
using Microsoft.Framework.Expiration.Interfaces;
using TagHelperSample.Web.Services;
namespace TagHelperSample.Web.Components
{
[ViewComponent(Name = "FeaturedMovies")]
public class FeaturedMoviesComponent : ViewComponent
{
private MoviesService _moviesService;
public FeaturedMoviesComponent(MoviesService moviesService)
{
_moviesService = moviesService;
}
public IViewComponentResult Invoke()
{
IExpirationTrigger trigger;
var movies = _moviesService.GetFeaturedMovies(out trigger);
// Add custom triggers
EntryLinkHelpers.ContextLink.AddExpirationTriggers(new[] { trigger });
return View(movies);
}
public IViewComponentResult Invoke(string movieName)
{
IExpirationTrigger trigger;
var quote = _moviesService.GetCriticsQuote(out trigger);
// This is invoked as part of a nested cache tag helper.
EntryLinkHelpers.ContextLink.AddExpirationTriggers(new[] { trigger });
return Content(quote);
}
}
}

View File

@ -0,0 +1,45 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc;
using TagHelperSample.Web.Services;
namespace TagHelperSample.Web.Controllers
{
public class MoviesController : Controller
{
private MoviesService _moviesService;
public MoviesController(MoviesService moviesService)
{
_moviesService = moviesService;
}
// Sample exhibiting the use of nested cache tag helpers with custom user expiration triggers.
// Trigger expirations cascade, expiration of the inner tag helper's content either due to absolute or sliding
// expiration or due to a user specified expiration trigger would cause the outer cache tag helper to also expire.
public ViewResult Index()
{
ViewData["Title"] = "Movies";
return View();
}
[HttpPost]
public ViewResult UpdateMovieRatings()
{
_moviesService.UpdateMovieRating();
ViewData["Title"] = "Movies with updated ratings";
return View("Index");
}
[HttpPost]
public ViewResult UpdateCriticsQuotes()
{
_moviesService.UpdateCriticsQuotes();
ViewData["Title"] = "Movies with updated critics quotes";
return View("Index");
}
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace TagHelperSample.Web.Models
{
public class FeaturedMovies
{
public string Name { get; set; }
public string Description { get; set; }
public int Rank { get; set; }
}
}

View File

@ -0,0 +1,68 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Microsoft.Framework.Cache.Memory;
using Microsoft.Framework.Expiration.Interfaces;
using TagHelperSample.Web.Models;
namespace TagHelperSample.Web.Services
{
public class MoviesService
{
private readonly Random _random = new Random();
private CancellationTokenSource _featuredMoviesTokenSource;
private CancellationTokenSource _quotesTokenSource;
public IEnumerable<FeaturedMovies> GetFeaturedMovies(out IExpirationTrigger expirationTrigger)
{
_featuredMoviesTokenSource = new CancellationTokenSource();
expirationTrigger = new CancellationTokenTrigger(_featuredMoviesTokenSource.Token);
return GetMovies().OrderBy(m => m.Rank).Take(2);
}
public void UpdateMovieRating()
{
_featuredMoviesTokenSource.Cancel();
_featuredMoviesTokenSource.Dispose();
_featuredMoviesTokenSource = null;
}
public string GetCriticsQuote(out IExpirationTrigger trigger)
{
_quotesTokenSource = new CancellationTokenSource();
var quotes = new[]
{
"A must see for iguana lovers everywhere",
"Slightly better than watching paint dry",
"Never felt more relieved seeing the credits roll",
"Bravo!"
};
trigger = new CancellationTokenTrigger(_quotesTokenSource.Token);
return quotes[_random.Next(0, quotes.Length)];
}
public void UpdateCriticsQuotes()
{
_quotesTokenSource.Cancel();
_quotesTokenSource.Dispose();
_quotesTokenSource = null;
}
private IEnumerable<FeaturedMovies> GetMovies()
{
yield return new FeaturedMovies { Name = "A day in the life of a blue whale", Rank = _random.Next(1, 10) };
yield return new FeaturedMovies { Name = "FlashForward", Rank = _random.Next(1, 10) };
yield return new FeaturedMovies { Name = "Frontier", Rank = _random.Next(1, 10) };
yield return new FeaturedMovies { Name = "Attack of the space spiders", Rank = _random.Next(1, 10) };
yield return new FeaturedMovies { Name = "Rift 3", Rank = _random.Next(1, 10) };
}
}
}

View File

@ -4,6 +4,7 @@
using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc;
using Microsoft.Framework.DependencyInjection;
using TagHelperSample.Web.Services;
namespace TagHelperSample.Web
{
@ -14,12 +15,23 @@ namespace TagHelperSample.Web
app.UseServices(services =>
{
services.AddMvc();
// Setup services with a test AssemblyProvider so that only the sample's assemblies are loaded. This
// prevents loading controllers from other assemblies when the sample is used in functional tests.
services.AddTransient<IAssemblyProvider, TestAssemblyProvider<Startup>>();
services.AddSingleton<MoviesService>();
services.Configure<MvcOptions>(options =>
{
options.AddXmlDataContractSerializerFormatter();
});
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
app.UseMvc();
}
}
}

View File

@ -15,4 +15,9 @@
<DevelopmentServerPort>30540</DevelopmentServerPort>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
<ProjectExtensions>
<VisualStudio>
<UserProperties project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@ -13,14 +13,15 @@
<form asp-anti-forgery="false" asp-action="Create">
<div class="form-horizontal">
@* validation summary tag helper will target just <div/> elements and append the list of errors *@
@* - i.e. this helper, like <select/> helper, has ContentBehavior.Append *@
@* helper does nothing if model is valid and (client-side validation is disabled or asp-validation-summary="ModelOnly") *@
@* - i.e. this helper, like <select/> helper appends content. *@
@* helper does nothing if model is valid and (client-side validation is disabled or
asp-validation-summary="ValidationSummary.ModelOnly") *@
@* don't need a bound attribute to match Html.ValidationSummary()'s headerTag parameter; users wrap message as they wish *@
@* initially at least, will not remove the <div/> if list isn't generated *@
@* - should helper remove the <div/> if list isn't generated? *@
@* - (Html.ValidationSummary returns empty string despite non-empty message parameter) *@
@* Acceptable values are: "None", "ModelOnly" and "All" *@
<div asp-validation-summary="All" style="color:blue" id="validation_day" class="form-group">
<div asp-validation-summary="ValidationSummary.All" style="color:blue" id="validation_day" class="form-group">
<span style="color:red">This is my message</span>
</div>
@ -47,8 +48,9 @@
<div class="form-group">
<label asp-for="YearsEmployeed" class="control-label col-md-2" />
<div class="col-md-10">
@* <select/> tag helper has ContentBehavior.Append -- items render after static options *@
<select asp-for="YearsEmployeed" asp-items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control">
@* <select/> items render after static options *@
@{ var @object = "multiple"; }
<select asp-for="@(Model.YearsEmployeed)" asp-items="@((IEnumerable<SelectListItem>)ViewBag.Items)" size="2" class="form-control" multiple="@(@object)">
@* Static use of "selected" attribute may cause HTML errors if in a single-selection <select/> *@
@* - @NTaylorMullen thinks <option/> tag helper could tell <select/> helper not to select anything from "items" *@
@* - wouldn't help if user selected one static <option/> and expression indicated another, especially one earlier in the <select/> *@

View File

@ -6,7 +6,7 @@
<form>
<div class="form-horizontal">
<div asp-validation-summary="All" />
<div asp-validation-summary="ValidationSummary.All" />
<input type="hidden" asp-for="Id" />
<div class="form-group">

View File

@ -9,28 +9,20 @@
@for (var index = 0; index < Model.Count(); ++index)
{
<div class="form-group">
@Html.LabelFor(m => m[index].Name)
@Html.TextBoxFor(m => m[index].Name, htmlAttributes: new { disabled = "disabled", @readonly= "readonly" })
@*<label asp-for="[index].Name" />
<input type="text" asp-for="[index].Name" disabled="disabled" readonly="readonly" />*@
<label asp-for="@Model[index].Name" />
<input type="text" asp-for="@Model[index].Name" disabled="disabled" readonly="readonly" />
</div>
<div class="form-group">
@Html.LabelFor(m => m[index].DateOfBirth)
@Html.TextBoxFor(m => m[index].DateOfBirth, format: "{0:yyyy-MM-dd}", htmlAttributes: new { disabled = "disabled", @readonly = "readonly", type="date" })
@*<label asp-for="[index].DateOfBirth" />
<input type="date" asp-for="[index].DateOfBirth" disabled="disabled" readonly="readonly" />*@
<label asp-for="@Model[index].DateOfBirth" />
<input type="date" asp-for="@Model[index].DateOfBirth" asp-format="{0:yyyy-MM-dd}" disabled="disabled" readonly="readonly" />
</div>
<div class="form-group">
@Html.LabelFor(m => m[index].YearsEmployeed)
@Html.TextBoxFor(m => m[index].YearsEmployeed, htmlAttributes: new { disabled = "disabled", @readonly = "readonly", type="number" })
@*<label asp-for="[index].YearsEmployeed" />
<input type="number" asp-for="[index].YearsEmployeed" disabled="disabled" readonly="readonly" />*@
<label asp-for="@Model[index].YearsEmployeed" />
<input type="number" asp-for="@Model[index].YearsEmployeed" disabled="disabled" readonly="readonly" />
</div>
<div class="form-group">
@Html.LabelFor(m => m[index].Blurb)
@Html.TextAreaFor(m => m[index].Blurb, htmlAttributes: new { disabled = "disabled", @readonly = "readonly" })
@*<label asp-for="[index].Blurb" />
<textarea rows="4" asp-for="[index].Blurb" disabled="disabled" readonly="readonly" />*@
<label asp-for="@Model[index].Blurb" />
<textarea rows="4" asp-for="@Model[index].Blurb" disabled="disabled" readonly="readonly" />
</div>
<p>

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html>
<head>
<title>@ViewBag.Title</title>
<style>
body {
width: 1040px;
}
.main {
width: 800px;
float: left;
}
.sidebar {
width: 200px;
float: left;
}
</style>
</head>
<body>
<div class="main">
<h3>Watch the greatest movies right here!</h3>
Submit your movie rankings:
<form asp-anti-forgery="false" asp-action="UpdateMovieRatings">
Movies + ratings go here
<button type="submit">Update ratings</button>
</form>
<form asp-anti-forgery="false" asp-action="UpdateCriticsQuotes">
Movies + ratings go here
<button type="submit">Update quotes</button>
</form>
</div>
<div class="sidebar">
<cache expires-after="TimeSpan.FromMinutes(20)">
@await Component.InvokeAsync("FeaturedMovies")
</cache>
</div>
<div style="clear: left"></div>
</body>
</html>

View File

@ -0,0 +1,17 @@
@model IEnumerable<TagHelperSample.Web.Models.FeaturedMovies>
<dl>
@foreach (var movie in Model)
{
<dt>(@movie.Rank) @movie.Name</dt>
<dd>
<div>
@movie.Description
</div>
<em>Critics say:</em>
<cache vary-by="@movie.Name">
@Component.Invoke("FeaturedMovies", movie.Name)
</cache>
</dd>
}
</dl>

View File

@ -1,19 +1,22 @@
{
"webroot": ".",
"compilationOptions": {
"warningsAsErrors": true
},
"dependencies": {
"Microsoft.AspNet.Mvc": "6.0.0-*",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*"
},
"commands": {
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001"
},
"frameworks": {
"aspnet50": {},
"aspnetcore50": {}
}
"commands": {
"web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001",
"kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000"
},
"compilationOptions": {
"warningsAsErrors": true
},
"dependencies": {
"Kestrel": "1.0.0-*",
"Microsoft.AspNet.Mvc": "6.0.0-*",
"Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*",
"Microsoft.AspNet.Server.IIS": "1.0.0-*",
"Microsoft.AspNet.Server.WebListener": "1.0.0-*",
"Microsoft.AspNet.StaticFiles": "1.0.0-*"
},
"frameworks": {
"aspnet50": { },
"aspnetcore50": { }
},
"webroot": "wwwroot"
}

Binary file not shown.

View File

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
@ -14,4 +14,9 @@
<SchemaVersion>2.0</SchemaVersion>
</PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" />
</Project>
<ProjectExtensions>
<VisualStudio>
<UserProperties project_1json__JSONSchema="http://www.asp.net/media/4878834/project.json" />
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@ -0,0 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Microsoft.AspNet.Mvc.Common.Test")]
[assembly: InternalsVisibleTo("Microsoft.AspNet.Mvc.Razor.Host")]

View File

@ -0,0 +1,58 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
namespace Microsoft.AspNet.Mvc
{
internal static class StringHelper
{
public static string TrimSpacesAndChars(string value, params char[] chars)
{
if (string.IsNullOrWhiteSpace(value))
{
return string.Empty;
}
if (chars == null || chars.Length == 0)
{
return value.Trim();
}
var firstIndex = 0;
for (; firstIndex < value.Length; firstIndex++)
{
var currentChar = value[firstIndex];
if (!char.IsWhiteSpace(currentChar) && !chars.Any(compareChar => compareChar == currentChar))
{
break;
}
}
// We trimmed all the way
if (firstIndex == value.Length)
{
return string.Empty;
}
var lastIndex = value.Length - 1;
for (; lastIndex > firstIndex; lastIndex--)
{
var currentChar = value[lastIndex];
if (!char.IsWhiteSpace(currentChar) && !chars.Any(compareChar => compareChar == currentChar))
{
break;
}
}
if (firstIndex == 0 && lastIndex == value.Length - 1)
{
return value;
}
else
{
return value.Substring(firstIndex, lastIndex - firstIndex + 1);
}
}
}
}

View File

@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc
return false;
}
for (int idx = 0; idx < t1.Length; ++idx)
for (var idx = 0; idx < t1.Length; ++idx)
{
if (t1[idx] != t2[idx])
{

View File

@ -5,7 +5,8 @@
},
"frameworks": {
"aspnet50": {},
"net45": { },
"aspnet50": { },
"aspnetcore50": {
"dependencies": {
"System.Reflection.Extensions": "4.0.0-beta-*",

View File

@ -0,0 +1,160 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Specifies the allowed content types which can be used to select the action based on request's content-type.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ConsumesAttribute : Attribute, IResourceFilter, IConsumesActionConstraint
{
public static readonly int ConsumesActionConstraintOrder = 200;
/// <summary>
/// Creates a new instance of <see cref="ConsumesAttribute"/>.
/// </summary>
public ConsumesAttribute([NotNull] string contentType, params string[] otherContentTypes)
{
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.
/// <inheritdoc />
int IActionConstraint.Order { get; } = ConsumesActionConstraintOrder;
/// <inheritdoc />
public IList<MediaTypeHeaderValue> ContentTypes { get; set; }
/// <inheritdoc />
public void OnResourceExecuting([NotNull] ResourceExecutingContext 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))
{
MediaTypeHeaderValue requestContentType = null;
MediaTypeHeaderValue.TryParse(context.HttpContext.Request.ContentType, out requestContentType);
// Only execute if this is the last filter before calling the action.
// This ensures that we only run the filter which is closest to the action.
if (requestContentType != null &&
!ContentTypes.Any(contentType => contentType.IsSubsetOf(requestContentType)))
{
context.Result = new UnsupportedMediaTypeResult();
}
}
}
/// <inheritdoc />
public void OnResourceExecuted([NotNull] ResourceExecutedContext 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;
}
MediaTypeHeaderValue requestContentType = null;
MediaTypeHeaderValue.TryParse(context.RouteContext.HttpContext.Request.ContentType, out requestContentType);
// 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;
}
if (ContentTypes.Any(c => c.IsSubsetOf(requestContentType)))
{
return true;
}
var firstCandidate = context.Candidates[0];
if (firstCandidate != context.CurrentCandidate)
{
// 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 == firstCandidate)
{
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 List<MediaTypeHeaderValue> GetContentTypes(string firstArg, string[] args)
{
var contentTypes = new List<MediaTypeHeaderValue>();
contentTypes.Add(MediaTypeHeaderValue.Parse(firstArg));
foreach (var item in args)
{
var contentType = MediaTypeHeaderValue.Parse(item);
contentTypes.Add(contentType);
}
return contentTypes;
}
}
}

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc
/// are candidates for selection, the next stage to run is the lowest value of <see cref="Order"/> 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 it's constraints in that stage executed.
/// 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

View File

@ -0,0 +1,13 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// An <see cref="IActionConstraint"/> constraint that identifies a type which can be used to select an action
/// based on incoming request.
/// </summary>
public interface IConsumesActionConstraint : IActionConstraint
{
}
}

View File

@ -14,7 +14,6 @@ namespace Microsoft.AspNet.Mvc
: this(actionContext.HttpContext, actionContext.RouteData, actionContext.ActionDescriptor)
{
ModelState = actionContext.ModelState;
Controller = actionContext.Controller;
}
public ActionContext([NotNull] RouteContext routeContext, [NotNull] ActionDescriptor actionDescriptor)
@ -39,16 +38,5 @@ namespace Microsoft.AspNet.Mvc
public ModelStateDictionary ModelState { get; private set; }
public ActionDescriptor ActionDescriptor { get; private set; }
/// <summary>
/// Input formatters associated with this context.
/// The formatters are populated only after IInputFormattersProvider runs.
/// </summary>
public IList<IInputFormatter> InputFormatters { get; set; }
/// <summary>
/// The controller is available only after the controller factory runs.
/// </summary>
public object Controller { get; set; }
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// An <see cref="ObjectResult"/> that when executed will produce a Bad Request (400) response.
/// </summary>
public class BadRequestObjectResult : ObjectResult
{
/// <summary>
/// Creates a new <see cref="BadRequestObjectResult"/> instance.
/// </summary>
/// <param name="error">Contains the errors to be returned to the client.</param>
public BadRequestObjectResult(object error)
: base(error)
{
StatusCode = 400;
}
/// <summary>
/// Creates a new <see cref="BadRequestObjectResult"/> instance.
/// </summary>
/// <param name="modelState"><see cref="ModelStateDictionary"/> containing the validation errors.</param>
public BadRequestObjectResult([NotNull] ModelStateDictionary modelState)
: base(new SerializableError(modelState))
{
StatusCode = 400;
}
}
}

View File

@ -1,21 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net;
using Microsoft.AspNet.Mvc;
namespace System.Web.Http
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// An action result that returns an empty <see cref="HttpStatusCode.BadRequest"/> response.
/// A <see cref="HttpStatusCodeResult"/> that when
/// executed will produce a Bad Request (400) response.
/// </summary>
public class BadRequestResult : HttpStatusCodeResult
{
/// <summary>
/// Initializes a new instance of the <see cref="BadRequestResult"/> class.
/// Creates a new <see cref="BadRequestResult"/> instance.
/// </summary>
public BadRequestResult()
: base((int)HttpStatusCode.BadRequest)
: base(400)
{
}
}

View File

@ -4,6 +4,7 @@
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
@ -15,18 +16,36 @@ namespace Microsoft.AspNet.Mvc
public string ContentType { get; set; }
/// <summary>
/// Gets or sets the HTTP status code.
/// </summary>
public int? StatusCode { get; set; }
public override async Task ExecuteResultAsync([NotNull] ActionContext context)
{
var response = context.HttpContext.Response;
if (!string.IsNullOrEmpty(ContentType))
MediaTypeHeaderValue contentTypeHeader;
if (string.IsNullOrEmpty(ContentType))
{
response.ContentType = ContentType;
contentTypeHeader = new MediaTypeHeaderValue("text/plain");
}
else
{
contentTypeHeader = new MediaTypeHeaderValue(ContentType);
}
contentTypeHeader.Encoding = ContentEncoding ?? Encodings.UTF8EncodingWithoutBOM;
response.ContentType = contentTypeHeader.ToString();
if (StatusCode != null)
{
response.StatusCode = StatusCode.Value;
}
if (Content != null)
{
await response.WriteAsync(Content);
await response.WriteAsync(Content, contentTypeHeader.Encoding);
}
}
}

View File

@ -0,0 +1,77 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// An <see cref="ActionResult"/> that returns a Created (201) response with a Location header.
/// </summary>
public class CreatedAtActionResult : ObjectResult
{
/// <summary>
/// Initializes a new instance of the <see cref="CreatedAtActionResult"/> with the values
/// provided.
/// </summary>
/// <param name="actionName">The name of the action to use for generating the URL.</param>
/// <param name="controllerName">The name of the controller to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The value to format in the entity body.</param>
public CreatedAtActionResult(string actionName,
string controllerName,
object routeValues,
object value)
: base(value)
{
ActionName = actionName;
ControllerName = controllerName;
RouteValues = TypeHelper.ObjectToDictionary(routeValues);
StatusCode = 201;
}
/// <summary>
/// Gets or sets the <see cref="IUrlHelper" /> used to generate URLs.
/// </summary>
public IUrlHelper UrlHelper { get; set; }
/// <summary>
/// Gets or sets the name of the action to use for generating the URL.
/// </summary>
public string ActionName { get; set; }
/// <summary>
/// Gets or sets the name of the controller to use for generating the URL.
/// </summary>
public string ControllerName { get; set; }
/// <summary>
/// Gets or sets the route data to use for generating the URL.
/// </summary>
public IDictionary<string, object> RouteValues { get; set; }
/// <inheritdoc />
protected override void OnFormatting([NotNull] ActionContext context)
{
var request = context.HttpContext.Request;
var urlHelper = UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService<IUrlHelper>();
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.Set("Location", url);
}
}
}

View File

@ -0,0 +1,75 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// An <see cref="ActionResult"/> that returns a Created (201) response with a Location header.
/// </summary>
public class CreatedAtRouteResult : ObjectResult
{
/// <summary>
/// Initializes a new instance of the <see cref="CreatedAtRouteResult"/> class with the values
/// provided.
/// </summary>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The value to format in the entity body.</param>
public CreatedAtRouteResult(object routeValues, object value)
: this(routeName: null, routeValues: routeValues, value: value)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="CreatedAtRouteResult"/> class with the values
/// provided.
/// </summary>
/// <param name="routeName">The name of the route to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The value to format in the entity body.</param>
public CreatedAtRouteResult(string routeName,
object routeValues,
object value)
: base(value)
{
RouteName = routeName;
RouteValues = TypeHelper.ObjectToDictionary(routeValues);
StatusCode = 201;
}
/// <summary>
/// Gets or sets the <see cref="IUrlHelper" /> used to generate URLs.
/// </summary>
public IUrlHelper UrlHelper { get; set; }
/// <summary>
/// Gets or sets the name of the route to use for generating the URL.
/// </summary>
public string RouteName { get; set; }
/// <summary>
/// Gets or sets the route data to use for generating the URL.
/// </summary>
public IDictionary<string, object> RouteValues { get; set; }
/// <inheritdoc />
protected override void OnFormatting([NotNull] ActionContext context)
{
var request = context.HttpContext.Request;
var urlHelper = UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService<IUrlHelper>();
var url = urlHelper.RouteUrl(RouteName, RouteValues, request.Scheme, request.Host.ToUriComponent());
if (string.IsNullOrEmpty(url))
{
throw new InvalidOperationException(Resources.NoRoutesMatched);
}
context.HttpContext.Response.Headers.Set("Location", url);
}
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// An <see cref="ActionResult"/> that returns a Created (201) response with a Location header.
/// </summary>
public class CreatedResult : ObjectResult
{
private string _location;
/// <summary>
/// Initializes a new instance of the <see cref="CreatedResult"/> class with the values
/// provided.
/// </summary>
/// <param name="location">The location at which the content has been created.</param>
/// <param name="value">The value to format in the entity body.</param>
public CreatedResult([NotNull] string location, object value)
: base(value)
{
Location = location;
StatusCode = 201;
}
/// <summary>
/// Gets or sets the location at which the content has been created.
/// </summary>
public string Location
{
get
{
return _location;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_location = value;
}
}
/// <inheritdoc />
protected override void OnFormatting([NotNull] ActionContext context)
{
context.HttpContext.Response.Headers.Set("Location", Location);
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
@ -13,6 +14,19 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public class FileContentResult : FileResult
{
private byte[] _fileContents;
/// <summary>
/// Creates a new <see cref="FileContentResult"/> instance with
/// the provided <paramref name="fileContents"/>.
/// </summary>
/// <param name="fileContents">The bytes that represent the file contents.</param>
public FileContentResult([NotNull] byte[] fileContents)
: base(contentType: null)
{
FileContents = fileContents;
}
/// <summary>
/// Creates a new <see cref="FileContentResult"/> instance with
/// the provided <paramref name="fileContents"/> and the
@ -27,9 +41,24 @@ namespace Microsoft.AspNet.Mvc
}
/// <summary>
/// Gets the file contents.
/// Gets or sets the file contents.
/// </summary>
public byte[] FileContents { get; private set; }
public byte[] FileContents
{
get
{
return _fileContents;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_fileContents = value;
}
}
/// <inheritdoc />
protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)

View File

@ -5,17 +5,17 @@ using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.FileSystems;
using Microsoft.AspNet.FileProviders;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http;
using Microsoft.AspNet.HttpFeature;
using Microsoft.AspNet.Http.Interfaces;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Represents an <see cref="ActionResult"/> that when executed will
/// An <see cref="ActionResult"/> that when executed will
/// write a file from disk to the response using mechanisms provided
/// by the host.
/// </summary>
@ -23,16 +23,17 @@ namespace Microsoft.AspNet.Mvc
{
private const int DefaultBufferSize = 0x1000;
private string _fileName;
/// <summary>
/// Creates a new <see cref="FilePathResult"/> instance with
/// the provided <paramref name="fileName"/> and the
/// provided <paramref name="contentType"/>.
/// the provided <paramref name="fileName"/>
/// </summary>
/// <param name="fileName">The path to the file. The path must be an absolute
/// path. Relative and virtual paths are not supported.</param>
/// <param name="contentType">The Content-Type header of the response.</param>
public FilePathResult([NotNull] string fileName, [NotNull] string contentType)
: base(contentType)
public FilePathResult([NotNull] string fileName)
: base(contentType: null)
{
FileName = fileName;
}
@ -45,34 +46,45 @@ namespace Microsoft.AspNet.Mvc
/// <param name="fileName">The path to the file. The path must be an absolute
/// path. Relative and virtual paths are not supported.</param>
/// <param name="contentType">The Content-Type header of the response.</param>
public FilePathResult(
[NotNull] string fileName,
[NotNull] string contentType,
[NotNull] IFileSystem fileSystem)
public FilePathResult([NotNull] string fileName, string contentType)
: base(contentType)
{
FileName = fileName;
FileSystem = fileSystem;
}
/// <summary>
/// Gets the path to the file that will be sent back as the response.
/// Gets or sets the path to the file that will be sent back as the response.
/// </summary>
public string FileName { get; private set; }
public string FileName
{
get
{
return _fileName;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_fileName = value;
}
}
/// <summary>
/// Gets the <see cref="IFileSystem"/> used to resolve paths.
/// Gets or sets the <see cref="IFileProvider"/> used to resolve paths.
/// </summary>
public IFileSystem FileSystem { get; private set; }
public IFileProvider FileProvider { get; set; }
/// <inheritdoc />
protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)
{
var sendFile = response.HttpContext.GetFeature<IHttpSendFileFeature>();
var fileSystem = GetFileSystem(response.HttpContext.RequestServices);
var fileProvider = GetFileProvider(response.HttpContext.RequestServices);
var filePath = ResolveFilePath(fileSystem);
var filePath = ResolveFilePath(fileProvider);
if (sendFile != null)
{
@ -88,7 +100,7 @@ namespace Microsoft.AspNet.Mvc
}
}
internal string ResolveFilePath(IFileSystem fileSystem)
internal string ResolveFilePath(IFileProvider fileProvider)
{
// Let the file system try to get the file and if it can't,
// fallback to trying the path directly unless the path starts with '/'.
@ -105,10 +117,10 @@ namespace Microsoft.AspNet.Mvc
return path;
}
var fileInfo = fileSystem.GetFileInfo(path);
var fileInfo = fileProvider.GetFileInfo(path);
if (fileInfo.Exists)
{
// The path is relative and IFileSystem found the file, so return the full
// The path is relative and IFileProvider found the file, so return the full
// path.
return fileInfo.PhysicalPath;
}
@ -119,7 +131,6 @@ namespace Microsoft.AspNet.Mvc
throw new FileNotFoundException(message, path);
}
// Internal for unit testing purposes only
/// <summary>
/// Creates a normalized representation of the given <paramref name="path"/>. The default
/// implementation doesn't support files with '\' in the file name and treats the '\' as
@ -128,6 +139,7 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
/// <param name="path">The path to normalize.</param>
/// <returns>The normalized path.</returns>
// Internal for unit testing purposes only
protected internal virtual string NormalizePath([NotNull] string path)
{
// Unix systems support '\' as part of the file name. So '\' is not
@ -145,7 +157,7 @@ namespace Microsoft.AspNet.Mvc
if (path.StartsWith("~\\", StringComparison.Ordinal))
{
// ~\ is not a valid virtual path, and we don't want to replace '\' with '/' as it
// ofuscates the error, so just return the original path and throw at a later point
// obfuscates the error, so just return the original path and throw at a later point
// when we can't find the file.
return path;
}
@ -153,13 +165,13 @@ namespace Microsoft.AspNet.Mvc
return path.Replace('\\', '/');
}
// Internal for unit testing purposes only
/// <summary>
/// Determines if the provided path is absolute or relative. The default implementation considers
/// paths starting with '/' to be relative.
/// </summary>
/// <param name="path">The path to examine.</param>
/// <returns>True if the path is absolute.</returns>
// Internal for unit testing purposes only
protected internal virtual bool IsPathRooted([NotNull] string path)
{
// We consider paths to be rooted if they start with '<<VolumeLetter>>:' and do
@ -183,19 +195,17 @@ namespace Microsoft.AspNet.Mvc
path.StartsWith("\\\\", StringComparison.Ordinal);
}
private IFileSystem GetFileSystem(IServiceProvider requestServices)
private IFileProvider GetFileProvider(IServiceProvider requestServices)
{
if (FileSystem != null)
if (FileProvider != null)
{
return FileSystem;
return FileProvider;
}
// For right now until we can use IWebRootFileSystemProvider, see
// https://github.com/aspnet/Hosting/issues/86 for details.
var hostingEnvironment = requestServices.GetService<IHostingEnvironment>();
FileSystem = new PhysicalFileSystem(hostingEnvironment.WebRoot);
FileProvider = hostingEnvironment.WebRootFileProvider;
return FileSystem;
return FileProvider;
}
private static async Task CopyStreamToResponse(

View File

@ -6,6 +6,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNet.Http;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
@ -22,15 +23,15 @@ namespace Microsoft.AspNet.Mvc
/// the provided <paramref name="contentType"/>.
/// </summary>
/// <param name="contentType">The Content-Type header of the response.</param>
protected FileResult([NotNull] string contentType)
protected FileResult(string contentType)
{
ContentType = contentType;
}
/// <summary>
/// Gets the Content-Type header value that will be written to the response.
/// Gets or sets the Content-Type header value that will be written to the response.
/// </summary>
public string ContentType { get; private set; }
public string ContentType { get; set; }
/// <summary>
/// Gets the file name that will be used in the Content-Disposition header of the response.
@ -45,7 +46,11 @@ namespace Microsoft.AspNet.Mvc
public override Task ExecuteResultAsync([NotNull] ActionContext context)
{
var response = context.HttpContext.Response;
response.ContentType = ContentType;
if (ContentType != null)
{
response.ContentType = ContentType;
}
if (!string.IsNullOrEmpty(FileDownloadName))
{
@ -54,11 +59,12 @@ namespace Microsoft.AspNet.Mvc
// 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 headerValue = ContentDispositionUtil.GetHeaderValue(FileDownloadName);
context.HttpContext.Response.Headers.Set("Content-Disposition", headerValue);
var cd = new ContentDispositionHeaderValue("attachment");
cd.SetHttpFileName(FileDownloadName);
context.HttpContext.Response.Headers.Set(HeaderNames.ContentDisposition, cd.ToString());
}
// We aren't flowing the cancellation token appropiately, see
// We aren't flowing the cancellation token appropriately, see
// https://github.com/aspnet/Mvc/issues/743 for details.
return WriteFileAsync(response, CancellationToken.None);
}
@ -74,193 +80,5 @@ namespace Microsoft.AspNet.Mvc
/// A <see cref="Task"/> that will complete when the file has been written to the response.
/// </returns>
protected abstract Task WriteFileAsync(HttpResponse response, CancellationToken cancellation);
// This is a temporary implementation until we have the right abstractions in HttpAbstractions.
internal static class ContentDispositionUtil
{
private const string HexDigits = "0123456789ABCDEF";
private static void AddByteToStringBuilder(byte b, StringBuilder builder)
{
builder.Append('%');
int i = b;
AddHexDigitToStringBuilder(i >> 4, builder);
AddHexDigitToStringBuilder(i % 16, builder);
}
private static void AddHexDigitToStringBuilder(int digit, StringBuilder builder)
{
builder.Append(HexDigits[digit]);
}
private static string CreateRfc2231HeaderValue(string filename)
{
var builder = new StringBuilder("attachment; filename*=UTF-8''");
var filenameBytes = Encoding.UTF8.GetBytes(filename);
foreach (var b in filenameBytes)
{
if (IsByteValidHeaderValueCharacter(b))
{
builder.Append((char)b);
}
else
{
AddByteToStringBuilder(b, builder);
}
}
return builder.ToString();
}
public static string GetHeaderValue(string fileName)
{
// If fileName contains any Unicode characters, encode according
// to RFC 2231 (with clarifications from RFC 5987)
foreach (var c in fileName)
{
if ((int)c > 127)
{
return CreateRfc2231HeaderValue(fileName);
}
}
return CreateNonUnicodeCharactersHeaderValue(fileName);
}
private static string CreateNonUnicodeCharactersHeaderValue(string fileName)
{
var escapedFileName = EscapeFileName(fileName);
return string.Format("attachment; filename={0}", escapedFileName);
}
private static string EscapeFileName(string fileName)
{
var hasToBeQuoted = false;
// We can't break the loop earlier because we need to check the
// whole name for \n, in which case we need to provide a special
// encoding.
for (var i = 0; i < fileName.Length; i++)
{
if (fileName[i] == '\n')
{
// See RFC 2047 for more details
return GetRfc2047Base64EncodedWord(fileName);
}
// Control characters = (octets 0 - 31) and DEL (127)
if (char.IsControl(fileName[i]))
{
hasToBeQuoted = true;
}
switch (fileName[i])
{
case '(':
case ')':
case '<':
case '>':
case '@':
case ',':
case ';':
case ':':
case '\\':
case '/':
case '[':
case ']':
case '?':
case '=':
case '{':
case '}':
case ' ':
case '\t':
case '"':
hasToBeQuoted = true;
break;
default:
break;
}
}
return hasToBeQuoted ? QuoteFileName(fileName) : fileName;
}
private static string QuoteFileName(string fileName)
{
var builder = new StringBuilder();
builder.Append("\"");
for (var i = 0; i < fileName.Length; i++)
{
switch (fileName[i])
{
case '\\':
// Escape \
builder.Append("\\\\");
break;
case '"':
// Escape "
builder.Append("\\\"");
break;
default:
builder.Append(fileName[i]);
break;
}
}
builder.Append("\"");
return builder.ToString();
}
private static string GetRfc2047Base64EncodedWord(string fileName)
{
// See RFC 2047 for details. Section 8 for examples.
const string charset = "utf-8";
// B means Base64
const string encoding = "B";
var fileNameBytes = Encoding.UTF8.GetBytes(fileName);
var base64EncodedFileName = Convert.ToBase64String(fileNameBytes);
// Encoded words are defined as "=?{charset}?{encoding}?{encpoded value}?="
return string.Format("\"=?{0}?{1}?{2}?=\"", charset, encoding, base64EncodedFileName);
}
// Application of RFC 2231 Encoding to Hypertext Transfer Protocol (HTTP) Header Fields, sec. 3.2
// http://greenbytes.de/tech/webdav/draft-reschke-rfc2231-in-http-latest.html
private static bool IsByteValidHeaderValueCharacter(byte b)
{
if ((byte)'0' <= b && b <= (byte)'9')
{
return true; // is digit
}
if ((byte)'a' <= b && b <= (byte)'z')
{
return true; // lowercase letter
}
if ((byte)'A' <= b && b <= (byte)'Z')
{
return true; // uppercase letter
}
switch (b)
{
case (byte)'-':
case (byte)'.':
case (byte)'_':
case (byte)'~':
case (byte)':':
case (byte)'!':
case (byte)'$':
case (byte)'&':
case (byte)'+':
return true;
}
return false;
}
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@ -17,6 +18,19 @@ namespace Microsoft.AspNet.Mvc
// default buffer size as defined in BufferedStream type
private const int BufferSize = 0x1000;
private Stream _fileStream;
/// <summary>
/// Creates a new <see cref="FileStreamResult"/> instance with
/// the provided <paramref name="fileStream"/>.
/// </summary>
/// <param name="fileStream">The stream with the file.</param>
public FileStreamResult([NotNull] Stream fileStream)
: base(contentType: null)
{
FileStream = fileStream;
}
/// <summary>
/// Creates a new <see cref="FileStreamResult"/> instance with
/// the provided <paramref name="fileStream"/> and the
@ -31,9 +45,24 @@ namespace Microsoft.AspNet.Mvc
}
/// <summary>
/// Gets the stream with the file that will be sent back as the response.
/// Gets or sets the stream with the file that will be sent back as the response.
/// </summary>
public Stream FileStream { get; private set; }
public Stream FileStream
{
get
{
return _fileStream;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_fileStream = value;
}
}
/// <inheritdoc />
protected async override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)

View File

@ -2,10 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
@ -55,6 +56,11 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public IOutputFormatter Formatter { get; set; }
/// <summary>
/// Gets or sets the HTTP status code.
/// </summary>
public int? StatusCode { get; set; }
/// <summary>
/// Gets or sets the value to be formatted.
/// </summary>
@ -86,7 +92,16 @@ namespace Microsoft.AspNet.Mvc
Object = Value,
};
// JsonResult expects to always find a formatter, in contrast with ObjectResult, which might return
// a 406.
var formatter = SelectFormatter(objectResult, formatterContext);
Debug.Assert(formatter != null);
if (StatusCode != null)
{
context.HttpContext.Response.StatusCode = StatusCode.Value;
}
await formatter.WriteAsync(formatterContext);
}

View File

@ -1,23 +1,13 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
#if ASPNET50
using System.Net;
#endif
namespace Microsoft.AspNet.Mvc
{
public class NoContentResult : ActionResult
public class NoContentResult : HttpStatusCodeResult
{
public override void ExecuteResult([NotNull] ActionContext context)
public NoContentResult()
: base(204)
{
var response = context.HttpContext.Response;
#if ASPNET50
response.StatusCode = (int)HttpStatusCode.NoContent;
#else
response.StatusCode = 204;
#endif
}
}
}

View File

@ -5,13 +5,22 @@ using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
using Microsoft.AspNet.Http;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.OptionsModel;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
public class ObjectResult : ActionResult
{
public ObjectResult(object value)
{
Value = value;
Formatters = new List<IOutputFormatter>();
ContentTypes = new List<MediaTypeHeaderValue>();
}
public object Value { get; set; }
public IList<IOutputFormatter> Formatters { get; set; }
@ -20,12 +29,10 @@ namespace Microsoft.AspNet.Mvc
public Type DeclaredType { get; set; }
public ObjectResult(object value)
{
Value = value;
Formatters = new List<IOutputFormatter>();
ContentTypes = new List<MediaTypeHeaderValue>();
}
/// <summary>
/// Gets or sets the HTTP status code.
/// </summary>
public int? StatusCode { get; set; }
public override async Task ExecuteResultAsync(ActionContext context)
{
@ -45,27 +52,55 @@ namespace Microsoft.AspNet.Mvc
return;
}
if (StatusCode != null)
{
context.HttpContext.Response.StatusCode = (int)StatusCode;
}
OnFormatting(context);
await selectedFormatter.WriteAsync(formatterContext);
}
public virtual IOutputFormatter SelectFormatter(OutputFormatterContext formatterContext,
IEnumerable<IOutputFormatter> formatters)
{
var incomingAcceptHeader = HeaderParsingHelpers.GetAcceptHeaders(
formatterContext.ActionContext.HttpContext.Request.Accept);
var sortedAcceptHeaders = SortMediaTypeWithQualityHeaderValues(incomingAcceptHeader)
.Where(header => header.Quality != HttpHeaderUtilitites.NoMatch)
.ToArray();
var incomingAcceptHeaderMediaTypes = formatterContext.ActionContext.HttpContext.Request.GetTypedHeaders().Accept ??
new MediaTypeHeaderValue[] { };
// By default we want to ignore considering accept headers for content negotiation when
// they have a media type like */* in them. Browsers typically have these media types.
// In these cases we would want the first formatter in the list of output formatters to
// write the response. This default behavior can be changed through options, so checking here.
var options = formatterContext.ActionContext.HttpContext
.RequestServices
.GetRequiredService<IOptions<MvcOptions>>()
.Options;
var respectAcceptHeader = true;
if (options.RespectBrowserAcceptHeader == false
&& incomingAcceptHeaderMediaTypes.Any(mediaType => mediaType.MatchesAllTypes))
{
respectAcceptHeader = false;
}
IEnumerable<MediaTypeHeaderValue> sortedAcceptHeaderMediaTypes = null;
if (respectAcceptHeader)
{
sortedAcceptHeaderMediaTypes = SortMediaTypeHeaderValues(incomingAcceptHeaderMediaTypes)
.Where(header => header.Quality != HeaderQuality.NoMatch);
}
IOutputFormatter selectedFormatter = null;
if (ContentTypes == null || ContentTypes.Count == 0)
{
// Select based on sorted accept headers.
selectedFormatter = SelectFormatterUsingSortedAcceptHeaders(
formatterContext,
formatters,
sortedAcceptHeaders);
if (respectAcceptHeader)
{
// Select based on sorted accept headers.
selectedFormatter = SelectFormatterUsingSortedAcceptHeaders(
formatterContext,
formatters,
sortedAcceptHeaderMediaTypes);
}
if (selectedFormatter == null)
{
@ -113,16 +148,15 @@ namespace Microsoft.AspNet.Mvc
}
else
{
// Filter and remove accept headers which cannot support any of the user specified content types.
var filteredAndSortedAcceptHeaders = sortedAcceptHeaders
.Where(acceptHeader =>
ContentTypes
.Any(contentType =>
contentType.IsSubsetOf(acceptHeader)))
.ToArray();
if (filteredAndSortedAcceptHeaders.Length > 0)
if (respectAcceptHeader)
{
// Filter and remove accept headers which cannot support any of the user specified content types.
var filteredAndSortedAcceptHeaders = sortedAcceptHeaderMediaTypes
.Where(acceptHeader =>
ContentTypes
.Any(contentType =>
contentType.IsSubsetOf(acceptHeader)));
selectedFormatter = SelectFormatterUsingSortedAcceptHeaders(
formatterContext,
formatters,
@ -183,20 +217,14 @@ namespace Microsoft.AspNet.Mvc
return selectedFormatter;
}
private static MediaTypeWithQualityHeaderValue[] SortMediaTypeWithQualityHeaderValues
(IEnumerable<MediaTypeWithQualityHeaderValue> headerValues)
private static IEnumerable<MediaTypeHeaderValue> SortMediaTypeHeaderValues(
IEnumerable<MediaTypeHeaderValue> headerValues)
{
if (headerValues == null)
{
return new MediaTypeWithQualityHeaderValue[] { };
}
// Use OrderBy() instead of Array.Sort() as it performs fewer comparisons. In this case the comparisons
// are quite expensive so OrderBy() performs better.
return headerValues.OrderByDescending(headerValue =>
headerValue,
MediaTypeWithQualityHeaderValueComparer.QualityComparer)
.ToArray();
MediaTypeHeaderValueComparer.QualityComparer);
}
private IEnumerable<IOutputFormatter> GetDefaultFormatters(ActionContext context)
@ -216,5 +244,12 @@ namespace Microsoft.AspNet.Mvc
return formatters;
}
/// <summary>
/// This method is called before the formatter writes to the output stream.
/// </summary>
protected virtual void OnFormatting([NotNull] ActionContext context)
{
}
}
}

View File

@ -13,6 +13,11 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public class PartialViewResult : ActionResult
{
/// <summary>
/// Gets or sets the HTTP status code.
/// </summary>
public int? StatusCode { get; set; }
/// <summary>
/// Gets or sets the name of the partial view to render.
/// </summary>
@ -44,6 +49,11 @@ namespace Microsoft.AspNet.Mvc
.EnsureSuccessful()
.View;
if (StatusCode != null)
{
context.HttpContext.Response.StatusCode = StatusCode.Value;
}
using (view as IDisposable)
{
await ViewExecutor.ExecuteAsync(view, context, ViewData, contentType: null);

View File

@ -9,12 +9,14 @@ namespace Microsoft.AspNet.Mvc
{
public class RedirectResult : ActionResult
{
public RedirectResult(string url)
private string _url;
public RedirectResult([NotNull] string url)
: this(url, permanent: false)
{
}
public RedirectResult(string url, bool permanent)
public RedirectResult([NotNull] string url, bool permanent)
{
if (string.IsNullOrEmpty(url))
{
@ -25,18 +27,33 @@ namespace Microsoft.AspNet.Mvc
Url = url;
}
public bool Permanent { get; private set; }
public bool Permanent { get; set; }
public string Url { get; private set; }
public string Url
{
get
{
return _url;
}
set
{
if (string.IsNullOrEmpty(value))
{
throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, "value");
}
_url = value;
}
}
public IUrlHelper UrlHelper { get; set; }
public override void ExecuteResult([NotNull] ActionContext context)
{
var destinationUrl = Url;
var urlHelper = context.HttpContext
.RequestServices
.GetRequiredService<IUrlHelper>();
var urlHelper = GetUrlHelper(context);
// IsLocalUrl is called to handle Urls starting with '~/'.
var destinationUrl = Url;
if (urlHelper.IsLocalUrl(destinationUrl))
{
destinationUrl = urlHelper.Content(Url);
@ -44,5 +61,10 @@ namespace Microsoft.AspNet.Mvc
context.HttpContext.Response.Redirect(destinationUrl, Permanent);
}
private IUrlHelper GetUrlHelper(ActionContext context)
{
return UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService<IUrlHelper>();
}
}
}

View File

@ -4,41 +4,47 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
public class RedirectToActionResult : ActionResult
{
public RedirectToActionResult([NotNull] IUrlHelper urlHelper, string actionName,
string controllerName, IDictionary<string, object> routeValues)
: this(urlHelper, actionName, controllerName, routeValues, permanent: false)
public RedirectToActionResult(
string actionName,
string controllerName,
IDictionary<string, object> routeValues)
: this(actionName, controllerName, routeValues, permanent: false)
{
}
public RedirectToActionResult([NotNull] IUrlHelper urlHelper, string actionName,
string controllerName, IDictionary<string, object> routeValues, bool permanent)
public RedirectToActionResult(
string actionName,
string controllerName,
IDictionary<string, object> routeValues,
bool permanent)
{
UrlHelper = urlHelper;
ActionName = actionName;
ControllerName = controllerName;
RouteValues = routeValues;
Permanent = permanent;
}
public IUrlHelper UrlHelper { get; private set; }
public IUrlHelper UrlHelper { get; set; }
public string ActionName { get; private set; }
public string ActionName { get; set; }
public string ControllerName { get; private set; }
public string ControllerName { get; set; }
public IDictionary<string, object> RouteValues { get; private set; }
public IDictionary<string, object> RouteValues { get; set; }
public bool Permanent { get; private set; }
public bool Permanent { get; set; }
public override void ExecuteResult([NotNull] ActionContext context)
{
var destinationUrl = UrlHelper.Action(ActionName, ControllerName, RouteValues);
var urlHelper = GetUrlHelper(context);
var destinationUrl = urlHelper.Action(ActionName, ControllerName, RouteValues);
if (string.IsNullOrEmpty(destinationUrl))
{
throw new InvalidOperationException(Resources.NoRoutesMatched);
@ -46,5 +52,10 @@ namespace Microsoft.AspNet.Mvc
context.HttpContext.Response.Redirect(destinationUrl, Permanent);
}
private IUrlHelper GetUrlHelper(ActionContext context)
{
return UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService<IUrlHelper>();
}
}
}

View File

@ -4,47 +4,47 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
public class RedirectToRouteResult : ActionResult
{
public RedirectToRouteResult([NotNull] IUrlHelper urlHelper,
object routeValues)
: this(urlHelper, routeName: null, routeValues: routeValues)
public RedirectToRouteResult(object routeValues)
: this(routeName: null, routeValues: routeValues)
{
}
public RedirectToRouteResult([NotNull] IUrlHelper urlHelper,
string routeName,
object routeValues)
: this(urlHelper, routeName, routeValues, permanent: false)
public RedirectToRouteResult(
string routeName,
object routeValues)
: this(routeName, routeValues, permanent: false)
{
}
public RedirectToRouteResult([NotNull] IUrlHelper urlHelper,
string routeName,
object routeValues,
bool permanent)
public RedirectToRouteResult(
string routeName,
object routeValues,
bool permanent)
{
UrlHelper = urlHelper;
RouteName = routeName;
RouteValues = TypeHelper.ObjectToDictionary(routeValues);
Permanent = permanent;
}
public IUrlHelper UrlHelper { get; private set; }
public IUrlHelper UrlHelper { get; set; }
public string RouteName { get; private set; }
public string RouteName { get; set; }
public IDictionary<string, object> RouteValues { get; private set; }
public IDictionary<string, object> RouteValues { get; set; }
public bool Permanent { get; private set; }
public bool Permanent { get; set; }
public override void ExecuteResult([NotNull] ActionContext context)
{
var destinationUrl = UrlHelper.RouteUrl(RouteValues);
var urlHelper = GetUrlHelper(context);
var destinationUrl = urlHelper.RouteUrl(RouteValues);
if (string.IsNullOrEmpty(destinationUrl))
{
throw new InvalidOperationException(Resources.NoRoutesMatched);
@ -52,5 +52,10 @@ namespace Microsoft.AspNet.Mvc
context.HttpContext.Response.Redirect(destinationUrl, Permanent);
}
private IUrlHelper GetUrlHelper(ActionContext context)
{
return UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService<IUrlHelper>();
}
}
}

View File

@ -0,0 +1,104 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Defines a serializable container for storing ModelState information.
/// This information is stored as key/value pairs.
/// </summary>
[XmlRoot("Error")]
public sealed class SerializableError : Dictionary<string, object>, IXmlSerializable
{
/// <summary>
/// Initializes a new instance of the <see cref="SerializableError"/> class.
/// </summary>
public SerializableError()
: base(StringComparer.OrdinalIgnoreCase)
{
}
/// <summary>
/// Creates a new instance of <see cref="SerializableError"/>.
/// </summary>
/// <param name="modelState"><see cref="ModelState"/> containing the validation errors.</param>
public SerializableError([NotNull] ModelStateDictionary modelState)
: this()
{
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);
}
}
}
// <inheritdoc />
public XmlSchema GetSchema()
{
return null;
}
// <inheritdoc />
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();
Add(key, value);
reader.MoveToContent();
}
reader.ReadEndElement();
}
// <inheritdoc />
public void WriteXml(XmlWriter writer)
{
foreach (var keyValuePair in this)
{
var key = keyValuePair.Key;
var value = keyValuePair.Value;
writer.WriteStartElement(XmlConvert.EncodeLocalName(key));
if (value != null)
{
writer.WriteValue(value);
}
writer.WriteEndElement();
}
}
}
}

View File

@ -0,0 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// A <see cref="HttpStatusCodeResult"/> that when
/// executed will produce a UnsupportedMediaType (415) response.
/// </summary>
public class UnsupportedMediaTypeResult : HttpStatusCodeResult
{
/// <summary>
/// Creates a new instance of <see cref="UnsupportedMediaTypeResult"/>.
/// </summary>
public UnsupportedMediaTypeResult() : base(415)
{
}
}
}

View File

@ -14,7 +14,12 @@ namespace Microsoft.AspNet.Mvc
public class ViewResult : ActionResult
{
/// <summary>
/// /// Gets or sets the name of the view to render.
/// Gets or sets the HTTP status code.
/// </summary>
public int? StatusCode { get; set; }
/// <summary>
/// Gets or sets the name of the view to render.
/// </summary>
/// <remarks>
/// When <c>null</c>, defaults to <see cref="ActionDescriptor.Name"/>.
@ -44,6 +49,11 @@ namespace Microsoft.AspNet.Mvc
.EnsureSuccessful()
.View;
if (StatusCode != null)
{
context.HttpContext.Response.StatusCode = StatusCode.Value;
}
using (view as IDisposable)
{
await ViewExecutor.ExecuteAsync(view, context, ViewData, contentType: null);

View File

@ -97,5 +97,14 @@ namespace Microsoft.AspNet.Mvc
{
Validate(context, antiForgeryTokenSet.CookieToken, antiForgeryTokenSet.FormToken);
}
/// <summary>
/// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers.
/// </summary>
/// <param name="context">The HTTP context associated with the current call.</param>
public void SetCookieTokenAndHeader([NotNull] HttpContext context)
{
_worker.SetCookieTokenAndHeader(context);
}
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
namespace Microsoft.AspNet.Mvc
{
internal sealed class AntiForgeryToken

View File

@ -23,7 +23,8 @@ namespace Microsoft.AspNet.Mvc
public AntiForgeryToken GetCookieToken(HttpContext httpContext)
{
var contextAccessor = httpContext.RequestServices.GetRequiredService<IContextAccessor<AntiForgeryContext>>();
var contextAccessor =
httpContext.RequestServices.GetRequiredService<IScopedInstance<AntiForgeryContext>>();
if (contextAccessor.Value != null)
{
return contextAccessor.Value.CookieToken;
@ -41,7 +42,7 @@ namespace Microsoft.AspNet.Mvc
public async Task<AntiForgeryToken> GetFormTokenAsync(HttpContext httpContext)
{
var form = await httpContext.Request.GetFormAsync();
var form = await httpContext.Request.ReadFormAsync();
var value = form[_config.FormFieldName];
if (string.IsNullOrEmpty(value))
{
@ -56,9 +57,10 @@ namespace Microsoft.AspNet.Mvc
{
// Add the cookie to the request based context.
// This is useful if the cookie needs to be reloaded in the context of the same request.
var contextAccessor = httpContext.RequestServices.GetRequiredService<IContextAccessor<AntiForgeryContext>>();
var contextAccessor =
httpContext.RequestServices.GetRequiredService<IScopedInstance<AntiForgeryContext>>();
Debug.Assert(contextAccessor.Value == null, "AntiForgeryContext should be set only once per request.");
contextAccessor.SetValue(new AntiForgeryContext() { CookieToken = token });
contextAccessor.Value = new AntiForgeryContext() { CookieToken = token };
var serializedToken = _serializer.Serialize(token);
var options = new CookieOptions() { HttpOnly = true };

View File

@ -102,19 +102,8 @@ namespace Microsoft.AspNet.Mvc
var tokenSet = GetTokens(httpContext, oldCookieToken);
var newCookieToken = tokenSet.CookieToken;
var formToken = tokenSet.FormToken;
if (newCookieToken != null)
{
// If a new cookie was generated, persist it.
_tokenStore.SaveCookieToken(httpContext, newCookieToken);
}
if (!_config.SuppressXFrameOptionsHeader)
{
// Adding X-Frame-Options header to prevent ClickJacking. See
// http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10
// for more information.
httpContext.Response.Headers.Set("X-Frame-Options", "SAMEORIGIN");
}
SaveCookieTokenAndHeader(httpContext, newCookieToken);
// <input type="hidden" name="__AntiForgeryToken" value="..." />
var retVal = new TagBuilder("input");
@ -143,15 +132,11 @@ namespace Microsoft.AspNet.Mvc
private AntiForgeryTokenSetInternal GetTokens(HttpContext httpContext, AntiForgeryToken oldCookieToken)
{
AntiForgeryToken newCookieToken = null;
if (!_validator.IsCookieTokenValid(oldCookieToken))
var newCookieToken = ValidateAndGenerateNewToken(oldCookieToken);
if (newCookieToken != null)
{
// Need to make sure we're always operating with a good cookie token.
oldCookieToken = newCookieToken = _generator.GenerateCookieToken();
oldCookieToken = newCookieToken;
}
Debug.Assert(_validator.IsCookieTokenValid(oldCookieToken));
var formToken = _generator.GenerateFormToken(
httpContext,
ExtractIdentity(httpContext),
@ -204,6 +189,54 @@ namespace Microsoft.AspNet.Mvc
deserializedFormToken);
}
/// <summary>
/// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers.
/// </summary>
/// <param name="context">The HTTP context associated with the current call.</param>
public void SetCookieTokenAndHeader([NotNull] HttpContext httpContext)
{
CheckSSLConfig(httpContext);
var oldCookieToken = GetCookieTokenNoThrow(httpContext);
var newCookieToken = ValidateAndGenerateNewToken(oldCookieToken);
SaveCookieTokenAndHeader(httpContext, newCookieToken);
}
// This method returns null if oldCookieToken is valid.
private AntiForgeryToken ValidateAndGenerateNewToken(AntiForgeryToken oldCookieToken)
{
if (!_validator.IsCookieTokenValid(oldCookieToken))
{
// Need to make sure we're always operating with a good cookie token.
var newCookieToken = _generator.GenerateCookieToken();
Debug.Assert(_validator.IsCookieTokenValid(newCookieToken));
return newCookieToken;
}
return null;
}
private void SaveCookieTokenAndHeader(
[NotNull] HttpContext httpContext,
AntiForgeryToken newCookieToken)
{
if (newCookieToken != null)
{
// Persist the new cookie if it is not null.
_tokenStore.SaveCookieToken(httpContext, newCookieToken);
}
if (!_config.SuppressXFrameOptionsHeader)
{
// Adding X-Frame-Options header to prevent ClickJacking. See
// http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10
// for more information.
httpContext.Response.Headers.Set("X-Frame-Options", "SAMEORIGIN");
}
}
private class AntiForgeryTokenSetInternal
{
public AntiForgeryToken FormToken { get; set; }

View File

@ -20,13 +20,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
Filters = new List<IFilter>();
HttpMethods = new List<string>();
Parameters = new List<ParameterModel>();
RouteConstraints = new List<IRouteConstraintProvider>();
}
public ActionModel([NotNull] ActionModel other)
{
ActionMethod = other.ActionMethod;
ActionName = other.ActionName;
IsActionNameMatchRequired = other.IsActionNameMatchRequired;
// Not making a deep copy of the controller, this action still belongs to the same controller.
Controller = other.Controller;
@ -40,6 +40,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
// Make a deep copy of other 'model' types.
ApiExplorer = new ApiExplorerModel(other.ApiExplorer);
Parameters = new List<ParameterModel>(other.Parameters.Select(p => new ParameterModel(p)));
RouteConstraints = new List<IRouteConstraintProvider>(other.RouteConstraints);
if (other.AttributeRouteModel != null)
{
@ -47,7 +48,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
}
}
public List<IActionConstraintMetadata> ActionConstraints { get; private set; }
public IList<IActionConstraintMetadata> ActionConstraints { get; private set; }
public MethodInfo ActionMethod { get; }
@ -62,18 +63,18 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
/// </remarks>
public ApiExplorerModel ApiExplorer { get; set; }
public AttributeRouteModel AttributeRouteModel { get; set; }
public IReadOnlyList<object> Attributes { get; }
public ControllerModel Controller { get; set; }
public List<IFilter> Filters { get; private set; }
public IList<IFilter> Filters { get; private set; }
public List<string> HttpMethods { get; private set; }
public IList<string> HttpMethods { get; private set; }
public bool IsActionNameMatchRequired { get; set; }
public IList<ParameterModel> Parameters { get; private set; }
public List<ParameterModel> Parameters { get; private set; }
public AttributeRouteModel AttributeRouteModel { get; set; }
public IList<IRouteConstraintProvider> RouteConstraints { get; private set; }
}
}

View File

@ -13,8 +13,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
Filters = new List<IFilter>();
}
public List<ControllerModel> Controllers { get; private set; }
public IList<ControllerModel> Controllers { get; private set; }
public List<IFilter> Filters { get; private set; }
public IList<IFilter> Filters { get; private set; }
}
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Routing;
@ -123,28 +124,28 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
return right;
}
if (left.EndsWith("/", StringComparison.OrdinalIgnoreCase))
if (left.EndsWith("/", StringComparison.Ordinal))
{
return left + right;
}
// Both templates contain some text.
return left + '/' + right;
return left + "/" + right;
}
private static bool IsOverridePattern(string template)
{
return template != null &&
(template.StartsWith("~/", StringComparison.OrdinalIgnoreCase) ||
template.StartsWith("/", StringComparison.OrdinalIgnoreCase));
(template.StartsWith("~/", StringComparison.Ordinal) ||
template.StartsWith("/", StringComparison.Ordinal));
}
private static bool IsEmptyLeftSegment(string template)
{
return template == null ||
template.Equals(string.Empty, StringComparison.OrdinalIgnoreCase) ||
template.Equals("~/", StringComparison.OrdinalIgnoreCase) ||
template.Equals("/", StringComparison.OrdinalIgnoreCase);
template.Equals(string.Empty, StringComparison.Ordinal) ||
template.Equals("~/", StringComparison.Ordinal) ||
template.Equals("/", StringComparison.Ordinal);
}
private static string CleanTemplate(string result)
@ -157,17 +158,17 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
// 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.OrdinalIgnoreCase))
if (result.Equals("//", StringComparison.Ordinal))
{
return result;
}
var startIndex = 0;
if (result.StartsWith("/", StringComparison.OrdinalIgnoreCase))
if (result.StartsWith("/", StringComparison.Ordinal))
{
startIndex = 1;
}
else if (result.StartsWith("~/", StringComparison.OrdinalIgnoreCase))
else if (result.StartsWith("~/", StringComparison.Ordinal))
{
startIndex = 2;
}
@ -179,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
}
var subStringLength = result.Length - startIndex;
if (result.EndsWith("/", StringComparison.OrdinalIgnoreCase))
if (result.EndsWith("/", StringComparison.Ordinal))
{
subStringLength--;
}
@ -336,7 +337,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
var message = Resources.FormatAttributeRoute_TokenReplacement_ReplacementValueNotFound(
template,
token,
string.Join(", ", values.Keys));
string.Join(", ", values.Keys.OrderBy(k => k, StringComparer.OrdinalIgnoreCase)));
throw new InvalidOperationException(message);
}

View File

@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
AttributeRoutes = new List<AttributeRouteModel>();
ActionConstraints = new List<IActionConstraintMetadata>();
Filters = new List<IFilter>();
RouteConstraints = new List<RouteConstraintAttribute>();
RouteConstraints = new List<IRouteConstraintProvider>();
}
public ControllerModel([NotNull] ControllerModel other)
@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
ActionConstraints = new List<IActionConstraintMetadata>(other.ActionConstraints);
Attributes = new List<object>(other.Attributes);
Filters = new List<IFilter>(other.Filters);
RouteConstraints = new List<RouteConstraintAttribute>(other.RouteConstraints);
RouteConstraints = new List<IRouteConstraintProvider>(other.RouteConstraints);
// Make a deep copy of other 'model' types.
Actions = new List<ActionModel>(other.Actions.Select(a => new ActionModel(a)));
@ -44,9 +44,9 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
other.AttributeRoutes.Select(a => new AttributeRouteModel(a)));
}
public List<IActionConstraintMetadata> ActionConstraints { get; private set; }
public IList<IActionConstraintMetadata> ActionConstraints { get; private set; }
public List<ActionModel> Actions { get; private set; }
public IList<ActionModel> Actions { get; private set; }
/// <summary>
/// Gets or sets the <see cref="ApiExplorerModel"/> for this controller.
@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public ApplicationModel Application { get; set; }
public List<AttributeRouteModel> AttributeRoutes { get; private set; }
public IList<AttributeRouteModel> AttributeRoutes { get; private set; }
public IReadOnlyList<object> Attributes { get; }
@ -63,8 +63,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public TypeInfo ControllerType { get; private set; }
public List<IFilter> Filters { get; private set; }
public IList<IFilter> Filters { get; private set; }
public List<RouteConstraintAttribute> RouteConstraints { get; private set; }
public IList<IRouteConstraintProvider> RouteConstraints { get; private set; }
}
}

View File

@ -233,7 +233,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
typeInfo.GetRuntimeInterfaceMap(typeof(IDisposable)).TargetMethods[0] == methodInfo);
}
/// <summary>
/// Creates an <see cref="ActionModel"/> for the given <see cref="MethodInfo"/>.
/// </summary>
@ -252,13 +251,10 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
[NotNull] MethodInfo methodInfo,
[NotNull] IReadOnlyList<object> attributes)
{
var actionModel = new ActionModel(methodInfo, attributes)
{
IsActionNameMatchRequired = true,
};
var actionModel = new ActionModel(methodInfo, attributes);
actionModel.ActionConstraints.AddRange(attributes.OfType<IActionConstraintMetadata>());
actionModel.Filters.AddRange(attributes.OfType<IFilter>());
AddRange(actionModel.ActionConstraints, attributes.OfType<IActionConstraintMetadata>());
AddRange(actionModel.Filters, attributes.OfType<IFilter>());
var actionName = attributes.OfType<ActionNameAttribute>().FirstOrDefault();
if (actionName?.Name != null)
@ -283,12 +279,14 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
}
var httpMethods = attributes.OfType<IActionHttpMethodProvider>();
actionModel.HttpMethods.AddRange(
AddRange(actionModel.HttpMethods,
httpMethods
.Where(a => a.HttpMethods != null)
.SelectMany(a => a.HttpMethods)
.Distinct());
AddRange(actionModel.RouteConstraints, attributes.OfType<IRouteConstraintProvider>());
var routeTemplateProvider =
attributes
.OfType<IRouteTemplateProvider>()
@ -318,7 +316,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
parameterModel.BinderMetadata = attributes.OfType<IBinderMetadata>().FirstOrDefault();
parameterModel.ParameterName = parameterInfo.Name;
parameterModel.IsOptional = parameterInfo.HasDefaultValue;
return parameterModel;
}
@ -330,5 +327,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
routeTemplateProvider.Order == null &&
routeTemplateProvider.Name == null;
}
private static void AddRange<T>(IList<T> list, IEnumerable<T> items)
{
foreach (var item in items)
{
list.Add(item);
}
}
}
}

View File

@ -2,10 +2,14 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Microsoft.AspNet.Mvc.Description;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Logging;
using Microsoft.AspNet.Mvc.Routing;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Mvc.ApplicationModels
{
@ -15,14 +19,16 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public class DefaultControllerModelBuilder : IControllerModelBuilder
{
private readonly IActionModelBuilder _actionModelBuilder;
private readonly ILogger _logger;
/// <summary>
/// Creates a new <see cref="DefaultControllerModelBuilder"/>.
/// </summary>
/// <param name="actionModelBuilder">The <see cref="IActionModelBuilder"/> used to create actions.</param>
public DefaultControllerModelBuilder(IActionModelBuilder actionModelBuilder)
public DefaultControllerModelBuilder(IActionModelBuilder actionModelBuilder, ILoggerFactory loggerFactory)
{
_actionModelBuilder = actionModelBuilder;
_logger = loggerFactory.Create<DefaultControllerModelBuilder>();
}
/// <inheritdoc />
@ -61,24 +67,42 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
/// </remarks>
protected virtual bool IsController([NotNull] TypeInfo typeInfo)
{
if (!typeInfo.IsClass ||
typeInfo.IsAbstract ||
var status = ControllerStatus.IsController;
// We only consider public top-level classes as controllers. IsPublic returns false for nested
// classes, regardless of visibility modifiers.
!typeInfo.IsPublic ||
typeInfo.ContainsGenericParameters)
if (!typeInfo.IsClass)
{
return false;
status |= ControllerStatus.IsNotAClass;
}
if (typeInfo.IsAbstract)
{
status |= ControllerStatus.IsAbstract;
}
// We only consider public top-level classes as controllers. IsPublic returns false for nested
// classes, regardless of visibility modifiers
if (!typeInfo.IsPublic)
{
status |= ControllerStatus.IsNotPublicOrTopLevel;
}
if (typeInfo.ContainsGenericParameters)
{
status |= ControllerStatus.ContainsGenericParameters;
}
if (typeInfo.Name.Equals("Controller", StringComparison.OrdinalIgnoreCase))
{
return false;
status |= ControllerStatus.NameIsController;
}
return typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) ||
typeof(Controller).GetTypeInfo().IsAssignableFrom(typeInfo);
if (!typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) &&
!typeof(Controller).GetTypeInfo().IsAssignableFrom(typeInfo))
{
status |= ControllerStatus.DoesNotEndWithControllerAndIsNotAssignable;
}
if (_logger.IsEnabled(LogLevel.Verbose))
{
_logger.WriteVerbose(new IsControllerValues(
typeInfo.AsType(),
status));
}
return status == ControllerStatus.IsController;
}
/// <summary>
@ -98,11 +122,12 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
typeInfo.Name.Substring(0, typeInfo.Name.Length - "Controller".Length) :
typeInfo.Name;
controllerModel.ActionConstraints.AddRange(attributes.OfType<IActionConstraintMetadata>());
controllerModel.Filters.AddRange(attributes.OfType<IFilter>());
controllerModel.RouteConstraints.AddRange(attributes.OfType<RouteConstraintAttribute>());
AddRange(controllerModel.ActionConstraints, attributes.OfType<IActionConstraintMetadata>());
AddRange(controllerModel.Filters, attributes.OfType<IFilter>());
AddRange(controllerModel.RouteConstraints, attributes.OfType<IRouteConstraintProvider>());
controllerModel.AttributeRoutes.AddRange(
AddRange(
controllerModel.AttributeRoutes,
attributes.OfType<IRouteTemplateProvider>().Select(rtp => new AttributeRouteModel(rtp)));
var apiVisibility = attributes.OfType<IApiDescriptionVisibilityProvider>().FirstOrDefault();
@ -117,7 +142,30 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
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;
}
private static void AddRange<T>(IList<T> list, IEnumerable<T> items)
{
foreach (var item in items)
{
list.Add(item);
}
}
}
}
}

View File

@ -22,7 +22,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
Action = other.Action;
Attributes = new List<object>(other.Attributes);
BinderMetadata = other.BinderMetadata;
IsOptional = other.IsOptional;
ParameterInfo = other.ParameterInfo;
ParameterName = other.ParameterName;
}
@ -33,8 +32,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
public IBinderMetadata BinderMetadata { get; set; }
public bool IsOptional { get; set; }
public ParameterInfo ParameterInfo { get; private set; }
public string ParameterName { get; set; }

View File

@ -16,10 +16,10 @@ using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
public class Controller : IActionFilter, IAsyncActionFilter, IOrderedFilter, IDisposable
public class Controller : IActionFilter, IAsyncActionFilter, IDisposable
{
private DynamicViewData _viewBag;
private IViewEngine _viewEngine;
private ViewDataDictionary _viewData;
public IServiceProvider Resolver
{
@ -59,28 +59,8 @@ namespace Microsoft.AspNet.Mvc
{
return ActionContext?.RouteData;
}
}
public IViewEngine ViewEngine
{
get
{
if (_viewEngine == null)
{
_viewEngine = ActionContext?.
HttpContext?.
RequestServices.GetRequiredService<ICompositeViewEngine>();
}
return _viewEngine;
}
set
{
_viewEngine = value;
}
}
public ModelStateDictionary ModelState
{
get
@ -93,10 +73,13 @@ namespace Microsoft.AspNet.Mvc
public ActionContext ActionContext { get; set; }
[Activate]
public IUrlHelper Url { get; set; }
public ActionBindingContext BindingContext { get; set; }
[Activate]
public IActionBindingContextProvider BindingContextProvider { get; set; }
public IModelMetadataProvider MetadataProvider { get; set; }
[Activate]
public IUrlHelper Url { get; set; }
public IPrincipal User
{
@ -106,8 +89,40 @@ namespace Microsoft.AspNet.Mvc
}
}
/// <summary>
/// Gets or sets <see cref="ViewDataDictionary"/> used by <see cref="ViewResult"/> and <see cref="ViewBag"/>.
/// </summary>
/// <remarks>
/// By default, this property is activated when <see cref="IControllerActivator"/> activates controllers.
/// However, when controllers are directly instantiated in user codes, this property is initialized with
/// <see cref="EmptyModelMetadataProvider"/>.
/// </remarks>
[Activate]
public ViewDataDictionary ViewData { get; set; }
public ViewDataDictionary ViewData
{
get
{
if (_viewData == null)
{
// This should run only for the controller unit test scenarios
_viewData =
new ViewDataDictionary(new EmptyModelMetadataProvider(),
ActionContext?.ModelState ?? new ModelStateDictionary());
}
return _viewData;
}
set
{
if (value == null)
{
throw
new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(ViewData));
}
_viewData = value;
}
}
public dynamic ViewBag
{
@ -122,15 +137,6 @@ namespace Microsoft.AspNet.Mvc
}
}
int IOrderedFilter.Order
{
get
{
// Controller-filter methods run farthest the action by default.
return int.MinValue;
}
}
/// <summary>
/// Creates a <see cref="ViewResult"/> object that renders a view to the response.
/// </summary>
@ -184,7 +190,6 @@ namespace Microsoft.AspNet.Mvc
{
ViewName = viewName,
ViewData = ViewData,
ViewEngine = _viewEngine,
};
}
@ -282,18 +287,10 @@ namespace Microsoft.AspNet.Mvc
var result = new ContentResult
{
Content = content,
ContentEncoding = contentEncoding,
ContentType = contentType
};
if (contentType != null)
{
result.ContentType = contentType;
}
if (contentEncoding != null)
{
result.ContentEncoding = contentEncoding;
}
return result;
}
@ -392,8 +389,10 @@ namespace Microsoft.AspNet.Mvc
public virtual RedirectToActionResult RedirectToAction(string actionName, string controllerName,
object routeValues)
{
return new RedirectToActionResult(Url, actionName, controllerName,
TypeHelper.ObjectToDictionary(routeValues));
return new RedirectToActionResult(actionName, controllerName, TypeHelper.ObjectToDictionary(routeValues))
{
UrlHelper = Url,
};
}
/// <summary>
@ -447,8 +446,14 @@ namespace Microsoft.AspNet.Mvc
public virtual RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName,
object routeValues)
{
return new RedirectToActionResult(Url, actionName, controllerName,
TypeHelper.ObjectToDictionary(routeValues), permanent: true);
return new RedirectToActionResult(
actionName,
controllerName,
TypeHelper.ObjectToDictionary(routeValues),
permanent: true)
{
UrlHelper = Url,
};
}
/// <summary>
@ -483,7 +488,10 @@ namespace Microsoft.AspNet.Mvc
[NonAction]
public virtual RedirectToRouteResult RedirectToRoute(string routeName, object routeValues)
{
return new RedirectToRouteResult(Url, routeName, routeValues);
return new RedirectToRouteResult(routeName, routeValues)
{
UrlHelper = Url,
};
}
/// <summary>
@ -520,7 +528,10 @@ namespace Microsoft.AspNet.Mvc
[NonAction]
public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, object routeValues)
{
return new RedirectToRouteResult(Url, routeName, routeValues, permanent: true);
return new RedirectToRouteResult(routeName, routeValues, permanent: true)
{
UrlHelper = Url,
};
}
/// <summary>
@ -617,6 +628,148 @@ namespace Microsoft.AspNet.Mvc
return new HttpNotFoundResult();
}
/// <summary>
/// Creates an <see cref="BadRequestResult"/> that produces a Bad Request (400) response.
/// </summary>
/// <returns>The created <see cref="BadRequestResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestResult HttpBadRequest()
{
return new BadRequestResult();
}
/// <summary>
/// Creates an <see cref="BadRequestObjectResult"/> that produces a Bad Request (400) response.
/// </summary>
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestObjectResult HttpBadRequest(object error)
{
return new BadRequestObjectResult(error);
}
/// <summary>
/// Creates an <see cref="BadRequestObjectResult"/> that produces a Bad Request (400) response.
/// </summary>
/// <returns>The created <see cref="BadRequestObjectResult"/> for the response.</returns>
[NonAction]
public virtual BadRequestObjectResult HttpBadRequest([NotNull] ModelStateDictionary modelState)
{
return new BadRequestObjectResult(modelState);
}
/// <summary>
/// Creates a <see cref="CreatedResult"/> object that produces a Created (201) response.
/// </summary>
/// <param name="uri">The URI at which the content has been created.</param>
/// <param name="value">The content value to format in the entity body.</param>
/// <returns>The created <see cref="CreatedResult"/> for the response.</returns>
[NonAction]
public virtual CreatedResult Created([NotNull] string uri, object value)
{
return new CreatedResult(uri, value);
}
/// <summary>
/// Creates a <see cref="CreatedResult"/> object that produces a Created (201) response.
/// </summary>
/// <param name="uri">The URI at which the content has been created.</param>
/// <param name="value">The content value to format in the entity body.</param>
/// <returns>The created <see cref="CreatedResult"/> for the response.</returns>
[NonAction]
public virtual CreatedResult Created([NotNull] Uri uri, object value)
{
string location;
if (uri.IsAbsoluteUri)
{
location = uri.AbsoluteUri;
}
else
{
location = uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped);
}
return new CreatedResult(location, value);
}
/// <summary>
/// Creates a <see cref="CreatedAtActionResult"/> object that produces a Created (201) response.
/// </summary>
/// <param name="actionName">The name of the action to use for generating the URL.</param>
/// <param name="value">The content value to format in the entity body.</param>
/// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual CreatedAtActionResult CreatedAtAction(string actionName, object value)
{
return CreatedAtAction(actionName, routeValues: null, value: value);
}
/// <summary>
/// Creates a <see cref="CreatedAtActionResult"/> object that produces a Created (201) response.
/// </summary>
/// <param name="actionName">The name of the action to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The content value to format in the entity body.</param>
/// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual CreatedAtActionResult CreatedAtAction(string actionName, object routeValues, object value)
{
return CreatedAtAction(actionName, controllerName: null, routeValues: routeValues, value: value);
}
/// <summary>
/// Creates a <see cref="CreatedAtActionResult"/> object that produces a Created (201) response.
/// </summary>
/// <param name="actionName">The name of the action to use for generating the URL.</param>
/// <param name="controllerName">The name of the controller to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The content value to format in the entity body.</param>
/// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual CreatedAtActionResult CreatedAtAction(string actionName,
string controllerName,
object routeValues,
object value)
{
return new CreatedAtActionResult(actionName, controllerName, routeValues, value);
}
/// <summary>
/// Creates a <see cref="CreatedAtRouteResult"/> object that produces a Created (201) response.
/// </summary>
/// <param name="routeName">The name of the route to use for generating the URL.</param>
/// <param name="value">The content value to format in the entity body.</param>
/// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object value)
{
return CreatedAtRoute(routeName, routeValues: null, value: value);
}
/// <summary>
/// Creates a <see cref="CreatedAtRouteResult"/> object that produces a Created (201) response.
/// </summary>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The content value to format in the entity body.</param>
/// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual CreatedAtRouteResult CreatedAtRoute(object routeValues, object value)
{
return CreatedAtRoute(routeName: null, routeValues: routeValues, value: value);
}
/// <summary>
/// Creates a <see cref="CreatedAtRouteResult"/> object that produces a Created (201) response.
/// </summary>
/// <param name="routeName">The name of the route to use for generating the URL.</param>
/// <param name="routeValues">The route data to use for generating the URL.</param>
/// <param name="value">The content value to format in the entity body.</param>
/// <returns>The created <see cref="CreatedAtRouteResult"/> for the response.</returns>
[NonAction]
public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object routeValues, object value)
{
return new CreatedAtRouteResult(routeName, routeValues, value);
}
/// <summary>
/// Called before the action method is invoked.
/// </summary>
@ -655,7 +808,7 @@ namespace Microsoft.AspNet.Mvc
}
/// <summary>
/// Updates the specified <paramref name="model"/> instance using values from the controller's current
/// Updates the specified <paramref name="model"/> instance using values from the controller's current
/// <see cref="IValueProvider"/>.
/// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam>
@ -682,15 +835,15 @@ namespace Microsoft.AspNet.Mvc
[NotNull] string prefix)
where TModel : class
{
if (BindingContextProvider == null)
if (BindingContext == null)
{
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
GetType().FullName);
var message = Resources.FormatPropertyOfTypeCannotBeNull(
nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
return await TryUpdateModelAsync(model, prefix, bindingContext.ValueProvider);
return await TryUpdateModelAsync(model, prefix, BindingContext.ValueProvider);
}
/// <summary>
@ -709,22 +862,23 @@ namespace Microsoft.AspNet.Mvc
[NotNull] IValueProvider valueProvider)
where TModel : class
{
if (BindingContextProvider == null)
if (BindingContext == null)
{
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
GetType().FullName);
var message = Resources.FormatPropertyOfTypeCannotBeNull(
nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
return await ModelBindingHelper.TryUpdateModelAsync(model,
prefix,
ActionContext.HttpContext,
ModelState,
bindingContext.MetadataProvider,
bindingContext.ModelBinder,
valueProvider,
bindingContext.ValidatorProvider);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
ActionContext.HttpContext,
ModelState,
MetadataProvider,
BindingContext.ModelBinder,
valueProvider,
BindingContext.ValidatorProvider);
}
/// <summary>
@ -745,23 +899,24 @@ namespace Microsoft.AspNet.Mvc
[NotNull] params Expression<Func<TModel, object>>[] includeExpressions)
where TModel : class
{
if (BindingContextProvider == null)
if (BindingContext == null)
{
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
GetType().FullName);
var message = Resources.FormatPropertyOfTypeCannotBeNull(
nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
return await ModelBindingHelper.TryUpdateModelAsync(model,
prefix,
ActionContext.HttpContext,
ModelState,
bindingContext.MetadataProvider,
bindingContext.ModelBinder,
bindingContext.ValueProvider,
bindingContext.ValidatorProvider,
includeExpressions);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
ActionContext.HttpContext,
ModelState,
MetadataProvider,
BindingContext.ModelBinder,
BindingContext.ValueProvider,
BindingContext.ValidatorProvider,
includeExpressions);
}
/// <summary>
@ -781,23 +936,24 @@ namespace Microsoft.AspNet.Mvc
[NotNull] Func<ModelBindingContext, string, bool> predicate)
where TModel : class
{
if (BindingContextProvider == null)
if (BindingContext == null)
{
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
GetType().FullName);
var message = Resources.FormatPropertyOfTypeCannotBeNull(
nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
return await ModelBindingHelper.TryUpdateModelAsync(model,
prefix,
ActionContext.HttpContext,
ModelState,
bindingContext.MetadataProvider,
bindingContext.ModelBinder,
bindingContext.ValueProvider,
bindingContext.ValidatorProvider,
predicate);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
ActionContext.HttpContext,
ModelState,
MetadataProvider,
BindingContext.ModelBinder,
BindingContext.ValueProvider,
BindingContext.ValidatorProvider,
predicate);
}
/// <summary>
@ -820,23 +976,24 @@ namespace Microsoft.AspNet.Mvc
[NotNull] params Expression<Func<TModel, object>>[] includeExpressions)
where TModel : class
{
if (BindingContextProvider == null)
if (BindingContext == null)
{
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
GetType().FullName);
var message = Resources.FormatPropertyOfTypeCannotBeNull(
nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
return await ModelBindingHelper.TryUpdateModelAsync(model,
prefix,
ActionContext.HttpContext,
ModelState,
bindingContext.MetadataProvider,
bindingContext.ModelBinder,
valueProvider,
bindingContext.ValidatorProvider,
includeExpressions);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
ActionContext.HttpContext,
ModelState,
MetadataProvider,
BindingContext.ModelBinder,
valueProvider,
BindingContext.ValidatorProvider,
includeExpressions);
}
/// <summary>
@ -858,23 +1015,75 @@ namespace Microsoft.AspNet.Mvc
[NotNull] Func<ModelBindingContext, string, bool> predicate)
where TModel : class
{
if (BindingContextProvider == null)
if (BindingContext == null)
{
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider),
GetType().FullName);
var message = Resources.FormatPropertyOfTypeCannotBeNull(
nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext);
return await ModelBindingHelper.TryUpdateModelAsync(model,
prefix,
ActionContext.HttpContext,
ModelState,
bindingContext.MetadataProvider,
bindingContext.ModelBinder,
valueProvider,
bindingContext.ValidatorProvider,
predicate);
return await ModelBindingHelper.TryUpdateModelAsync(
model,
prefix,
ActionContext.HttpContext,
ModelState,
MetadataProvider,
BindingContext.ModelBinder,
valueProvider,
BindingContext.ValidatorProvider,
predicate);
}
/// <summary>
/// Validates the specified <paramref name="model"/> instance.
/// </summary>
/// <param name="model">The model to validate.</param>
/// <returns><c>true</c> if the <see cref="ModelState"/> is valid; <c>false</c> otherwise. </returns>
[NonAction]
public virtual bool TryValidateModel([NotNull] object model)
{
return TryValidateModel(model, prefix: null);
}
/// <summary>
/// Validates the specified <paramref name="model"/> instance.
/// </summary>
/// <param name="model">The model to validate.</param>
/// <param name="prefix">The key to use when looking up information in <see cref="ModelState"/>.
/// </param>
/// <returns><c>true</c> if the <see cref="ModelState"/> is valid;<c>false</c> otherwise. </returns>
[NonAction]
public virtual bool TryValidateModel([NotNull] object model, string prefix)
{
if (BindingContext == null)
{
var message = Resources.FormatPropertyOfTypeCannotBeNull(
nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message);
}
var modelMetadata = MetadataProvider.GetMetadataForType(
modelAccessor: () => model,
modelType: model.GetType());
var validationContext = new ModelValidationContext(
MetadataProvider,
BindingContext.ValidatorProvider,
ModelState,
modelMetadata,
containerMetadata: null);
var modelName = prefix ?? string.Empty;
var validationNode = new ModelValidationNode(modelMetadata, modelName)
{
ValidateAllProperties = true
};
validationNode.Validate(validationContext);
return ModelState.IsValid;
}
public void Dispose()

View File

@ -56,11 +56,7 @@ namespace Microsoft.AspNet.Mvc
actionDescriptor.ControllerTypeInfo = controller.ControllerType;
AddApiExplorerInfo(actionDescriptor, action, controller);
AddRouteConstraints(actionDescriptor, controller, action);
AddControllerRouteConstraints(
actionDescriptor,
controller.RouteConstraints,
removalConstraints);
AddRouteConstraints(removalConstraints, actionDescriptor, controller, action);
if (IsAttributeRoutedAction(actionDescriptor))
{
@ -279,7 +275,6 @@ namespace Microsoft.AspNet.Mvc
var parameterDescriptor = new ParameterDescriptor()
{
BinderMetadata = parameter.BinderMetadata,
IsOptional = parameter.IsOptional,
Name = parameter.ParameterName,
ParameterType = parameter.ParameterInfo.ParameterType,
};
@ -295,6 +290,13 @@ namespace Microsoft.AspNet.Mvc
var apiExplorerIsVisible = action.ApiExplorer?.IsVisible ?? controller.ApiExplorer?.IsVisible ?? false;
if (apiExplorerIsVisible)
{
if (!IsAttributeRoutedAction(actionDescriptor))
{
// ApiExplorer is only supported on attribute routed actions.
throw new InvalidOperationException(Resources.FormatApiExplorer_UnsupportedAction(
actionDescriptor.DisplayName));
}
var apiExplorerActionData = new ApiDescriptionActionData()
{
GroupName = action.ApiExplorer?.GroupName ?? controller.ApiExplorer?.GroupName,
@ -371,39 +373,17 @@ namespace Microsoft.AspNet.Mvc
}
public static void AddRouteConstraints(
ISet<string> removalConstraints,
ControllerActionDescriptor actionDescriptor,
ControllerModel controller,
ActionModel action)
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
"controller",
controller.ControllerName));
if (action.IsActionNameMatchRequired)
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
"action",
action.ActionName));
}
else
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
"action",
string.Empty));
}
}
private static void AddControllerRouteConstraints(
ControllerActionDescriptor actionDescriptor,
IList<RouteConstraintAttribute> routeconstraints,
ISet<string> removalConstraints)
{
// Apply all the constraints defined on the controller (for example, [Area]) to the actions
// in that controller. Also keep track of all the constraints that require preventing actions
// 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 constraintAttribute in routeconstraints)
foreach (var constraintAttribute in action.RouteConstraints)
{
if (constraintAttribute.BlockNonAttributedActions)
{
@ -427,6 +407,46 @@ namespace Microsoft.AspNet.Mvc
}
}
}
foreach (var constraintAttribute in controller.RouteConstraints)
{
if (constraintAttribute.BlockNonAttributedActions)
{
removalConstraints.Add(constraintAttribute.RouteKey);
}
// Skip duplicates - this also means that a value on the action will take precedence
if (!HasConstraint(actionDescriptor.RouteConstraints, constraintAttribute.RouteKey))
{
if (constraintAttribute.RouteKeyHandling == RouteKeyHandling.CatchAll)
{
actionDescriptor.RouteConstraints.Add(
RouteDataActionConstraint.CreateCatchAll(
constraintAttribute.RouteKey));
}
else
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
constraintAttribute.RouteKey,
constraintAttribute.RouteValue));
}
}
}
// Lastly add the 'default' values
if (!HasConstraint(actionDescriptor.RouteConstraints, "action"))
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
"action",
action.ActionName ?? string.Empty));
}
if (!HasConstraint(actionDescriptor.RouteConstraints, "controller"))
{
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint(
"controller",
controller.ControllerName));
}
}
private static bool HasConstraint(List<RouteDataActionConstraint> constraints, string routeKey)

View File

@ -6,6 +6,8 @@ using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNet.Mvc.ApplicationModels;
using Microsoft.AspNet.Mvc.Filters;
using Microsoft.AspNet.Mvc.Logging;
using Microsoft.Framework.Logging;
using Microsoft.Framework.OptionsModel;
namespace Microsoft.AspNet.Mvc
@ -16,16 +18,19 @@ namespace Microsoft.AspNet.Mvc
private readonly IAssemblyProvider _assemblyProvider;
private readonly IReadOnlyList<IFilter> _globalFilters;
private readonly IEnumerable<IApplicationModelConvention> _modelConventions;
private readonly ILogger _logger;
public ControllerActionDescriptorProvider(IAssemblyProvider assemblyProvider,
IControllerModelBuilder applicationModelBuilder,
IGlobalFilterProvider globalFilters,
IOptions<MvcOptions> optionsAccessor)
public ControllerActionDescriptorProvider([NotNull] IAssemblyProvider assemblyProvider,
[NotNull] IControllerModelBuilder applicationModelBuilder,
[NotNull] IGlobalFilterProvider globalFilters,
[NotNull] IOptions<MvcOptions> optionsAccessor,
[NotNull] ILoggerFactory loggerFactory)
{
_assemblyProvider = assemblyProvider;
_applicationModelBuilder = applicationModelBuilder;
_globalFilters = globalFilters.Filters;
_modelConventions = optionsAccessor.Options.ApplicationModelConventions;
_logger = loggerFactory.Create<ControllerActionDescriptorProvider>();
}
public int Order
@ -43,16 +48,33 @@ namespace Microsoft.AspNet.Mvc
{
var applicationModel = BuildModel();
ApplicationModelConventions.ApplyConventions(applicationModel, _modelConventions);
if (_logger.IsEnabled(LogLevel.Verbose))
{
foreach (var controller in applicationModel.Controllers)
{
_logger.WriteVerbose(new ControllerModelValues(controller));
}
}
return ControllerActionDescriptorBuilder.Build(applicationModel);
}
public ApplicationModel BuildModel()
{
var applicationModel = new ApplicationModel();
applicationModel.Filters.AddRange(_globalFilters);
foreach (var filter in _globalFilters)
{
applicationModel.Filters.Add(filter);
}
var assemblies = _assemblyProvider.CandidateAssemblies;
var types = assemblies.SelectMany(a => a.DefinedTypes);
if (_logger.IsEnabled(LogLevel.Verbose))
{
foreach (var assembly in assemblies)
{
_logger.WriteVerbose(new AssemblyValues(assembly));
}
}
foreach (var type in types)
{

View File

@ -3,9 +3,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
@ -14,21 +15,32 @@ namespace Microsoft.AspNet.Mvc
{
private readonly ControllerActionDescriptor _descriptor;
private readonly IControllerFactory _controllerFactory;
private readonly IInputFormattersProvider _inputFormattersProvider;
private readonly IControllerActionArgumentBinder _actionInvocationProvider;
private readonly IControllerActionArgumentBinder _argumentBinder;
public ControllerActionInvoker([NotNull] ActionContext actionContext,
[NotNull] INestedProviderManager<FilterProviderContext> filterProvider,
[NotNull] IControllerFactory controllerFactory,
[NotNull] ControllerActionDescriptor descriptor,
[NotNull] IInputFormattersProvider inputFormattersProvider,
[NotNull] IControllerActionArgumentBinder controllerActionArgumentBinder)
: base(actionContext, filterProvider)
public ControllerActionInvoker(
[NotNull] ActionContext actionContext,
[NotNull] INestedProviderManager<FilterProviderContext> filterProvider,
[NotNull] IControllerFactory controllerFactory,
[NotNull] ControllerActionDescriptor descriptor,
[NotNull] IInputFormattersProvider inputFormatterProvider,
[NotNull] IControllerActionArgumentBinder controllerActionArgumentBinder,
[NotNull] IModelBinderProvider modelBinderProvider,
[NotNull] IModelValidatorProviderProvider modelValidatorProviderProvider,
[NotNull] IValueProviderFactoryProvider valueProviderFactoryProvider,
[NotNull] IScopedInstance<ActionBindingContext> actionBindingContextAccessor)
: base(
actionContext,
filterProvider,
inputFormatterProvider,
modelBinderProvider,
modelValidatorProviderProvider,
valueProviderFactoryProvider,
actionBindingContextAccessor)
{
_descriptor = descriptor;
_controllerFactory = controllerFactory;
_inputFormattersProvider = inputFormattersProvider;
_actionInvocationProvider = controllerActionArgumentBinder;
_argumentBinder = controllerActionArgumentBinder;
if (descriptor.MethodInfo == null)
{
throw new ArgumentException(
@ -38,20 +50,16 @@ namespace Microsoft.AspNet.Mvc
}
}
public async override Task InvokeAsync()
protected override object CreateInstance()
{
var controller = _controllerFactory.CreateController(ActionContext);
try
{
ActionContext.Controller = controller;
ActionContext.InputFormatters = _inputFormattersProvider.InputFormatters
.ToList();
await base.InvokeAsync();
}
finally
{
_controllerFactory.ReleaseController(ActionContext.Controller);
}
// The binding context is used in activation
Debug.Assert(ActionBindingContext != null);
return _controllerFactory.CreateController(ActionContext);
}
protected override void ReleaseInstance(object instance)
{
_controllerFactory.ReleaseController(instance);
}
protected override async Task<IActionResult> InvokeActionAsync(ActionExecutingContext actionExecutingContext)
@ -59,7 +67,7 @@ namespace Microsoft.AspNet.Mvc
var actionMethodInfo = _descriptor.MethodInfo;
var actionReturnValue = await ControllerActionExecutor.ExecuteAsync(
actionMethodInfo,
ActionContext.Controller,
actionExecutingContext.Controller,
actionExecutingContext.ActionArguments);
var actionResult = CreateActionResult(
@ -68,9 +76,11 @@ namespace Microsoft.AspNet.Mvc
return actionResult;
}
protected override Task<IDictionary<string, object>> GetActionArgumentsAsync(ActionContext context)
protected override Task<IDictionary<string, object>> GetActionArgumentsAsync(
ActionContext context,
ActionBindingContext bindingContext)
{
return _actionInvocationProvider.GetActionArgumentsAsync(context);
return _argumentBinder.GetActionArgumentsAsync(context, bindingContext);
}
// Marking as internal for Unit Testing purposes.

View File

@ -2,26 +2,40 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
{
public class ControllerActionInvokerProvider : IActionInvokerProvider
{
private readonly IControllerActionArgumentBinder _argumentBinder;
private readonly IControllerFactory _controllerFactory;
private readonly IInputFormattersProvider _inputFormattersProvider;
private readonly INestedProviderManager<FilterProviderContext> _filterProvider;
private readonly IControllerActionArgumentBinder _actionInvocationInfoProvider;
private readonly IInputFormattersProvider _inputFormattersProvider;
private readonly IModelBinderProvider _modelBinderProvider;
private readonly IModelValidatorProviderProvider _modelValidationProviderProvider;
private readonly IValueProviderFactoryProvider _valueProviderFactoryProvider;
private readonly IScopedInstance<ActionBindingContext> _actionBindingContextAccessor;
public ControllerActionInvokerProvider(IControllerFactory controllerFactory,
IInputFormattersProvider inputFormattersProvider,
INestedProviderManager<FilterProviderContext> filterProvider,
IControllerActionArgumentBinder actionInvocationInfoProvider)
public ControllerActionInvokerProvider(
IControllerFactory controllerFactory,
IInputFormattersProvider inputFormattersProvider,
INestedProviderManager<FilterProviderContext> filterProvider,
IControllerActionArgumentBinder argumentBinder,
IModelBinderProvider modelBinderProvider,
IModelValidatorProviderProvider modelValidationProviderProvider,
IValueProviderFactoryProvider valueProviderFactoryProvider,
IScopedInstance<ActionBindingContext> actionBindingContextAccessor)
{
_controllerFactory = controllerFactory;
_inputFormattersProvider = inputFormattersProvider;
_filterProvider = filterProvider;
_actionInvocationInfoProvider = actionInvocationInfoProvider;
_argumentBinder = argumentBinder;
_modelBinderProvider = modelBinderProvider;
_modelValidationProviderProvider = modelValidationProviderProvider;
_valueProviderFactoryProvider = valueProviderFactoryProvider;
_actionBindingContextAccessor = actionBindingContextAccessor;
}
public int Order
@ -41,7 +55,11 @@ namespace Microsoft.AspNet.Mvc
_controllerFactory,
actionDescriptor,
_inputFormattersProvider,
_actionInvocationInfoProvider);
_argumentBinder,
_modelBinderProvider,
_modelValidationProviderProvider,
_valueProviderFactoryProvider,
_actionBindingContextAccessor);
}
callNext();

View File

@ -2,7 +2,9 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using Microsoft.AspNet.Mvc.Logging;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;
namespace Microsoft.AspNet.Mvc
{
@ -13,15 +15,17 @@ namespace Microsoft.AspNet.Mvc
public class DefaultActionDescriptorsCollectionProvider : IActionDescriptorsCollectionProvider
{
private readonly IServiceProvider _serviceProvider;
private readonly ILogger _logger;
private ActionDescriptorsCollection _collection;
/// <summary>
/// Initializes a new instance of the <see cref="DefaultActionDescriptorsCollectionProvider" /> class.
/// </summary>
/// <param name="serviceProvider">The application IServiceProvider.</param>
public DefaultActionDescriptorsCollectionProvider(IServiceProvider serviceProvider)
public DefaultActionDescriptorsCollectionProvider(IServiceProvider serviceProvider, ILoggerFactory factory)
{
_serviceProvider = serviceProvider;
_logger = factory.Create<DefaultActionDescriptorsCollectionProvider>();
}
/// <summary>
@ -48,6 +52,14 @@ namespace Microsoft.AspNet.Mvc
actionDescriptorProvider.Invoke(actionDescriptorProviderContext);
if (_logger.IsEnabled(LogLevel.Verbose))
{
foreach (var actionDescriptor in actionDescriptorProviderContext.Results)
{
_logger.WriteVerbose(new ActionDescriptorValues(actionDescriptor));
}
}
return new ActionDescriptorsCollection(actionDescriptorProviderContext.Results, 0);
}
}

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc
public class DefaultAssemblyProvider : IAssemblyProvider
{
/// <summary>
/// Gets the set of assembly names that are used as root for discovery of
/// Gets the set of assembly names that are used as root for discovery of
/// MVC controllers, view components and views.
/// </summary>
protected virtual HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.Ordinal)
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc
/// <summary>
/// Returns a list of libraries that references the assemblies in <see cref="ReferenceAssemblies"/>.
/// By default it returns all assemblies that reference any of the primary MVC assemblies
/// By default it returns all assemblies that reference any of the primary MVC assemblies
/// while ignoring MVC assemblies.
/// </summary>
/// <returns>A set of <see cref="ILibraryInformation"/>.</returns>

View File

@ -15,18 +15,17 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
public class DefaultControllerActionArgumentBinder : IControllerActionArgumentBinder
{
private readonly IActionBindingContextProvider _bindingContextProvider;
private readonly IModelMetadataProvider _modelMetadataProvider;
public DefaultControllerActionArgumentBinder(IActionBindingContextProvider bindingContextProvider)
public DefaultControllerActionArgumentBinder(IModelMetadataProvider modelMetadataProvider)
{
_bindingContextProvider = bindingContextProvider;
_modelMetadataProvider = modelMetadataProvider;
}
public async Task<IDictionary<string, object>> GetActionArgumentsAsync(ActionContext actionContext)
public async Task<IDictionary<string, object>> GetActionArgumentsAsync(
ActionContext actionContext,
ActionBindingContext actionBindingContext)
{
var actionBindingContext = await _bindingContextProvider.GetActionBindingContextAsync(actionContext);
var metadataProvider = actionBindingContext.MetadataProvider;
var actionDescriptor = actionContext.ActionDescriptor as ControllerActionDescriptor;
if (actionDescriptor == null)
{
@ -39,7 +38,7 @@ namespace Microsoft.AspNet.Mvc
var parameterMetadata = new List<ModelMetadata>();
foreach (var parameter in actionDescriptor.Parameters)
{
var metadata = metadataProvider.GetMetadataForParameter(
var metadata = _modelMetadataProvider.GetMetadataForParameter(
modelAccessor: null,
methodInfo: actionDescriptor.MethodInfo,
parameterName: parameter.Name);
@ -52,7 +51,7 @@ namespace Microsoft.AspNet.Mvc
}
var actionArguments = new Dictionary<string, object>(StringComparer.Ordinal);
await PopulateArgumentAsync(actionBindingContext, actionArguments, parameterMetadata);
await PopulateArgumentAsync(actionContext, actionBindingContext, actionArguments, parameterMetadata);
return actionArguments;
}
@ -71,24 +70,26 @@ namespace Microsoft.AspNet.Mvc
}
private async Task PopulateArgumentAsync(
ActionBindingContext actionBindingContext,
ActionContext actionContext,
ActionBindingContext bindingContext,
IDictionary<string, object> arguments,
IEnumerable<ModelMetadata> parameterMetadata)
{
var operationBindingContext = new OperationBindingContext
{
ModelBinder = actionBindingContext.ModelBinder,
ValidatorProvider = actionBindingContext.ValidatorProvider,
MetadataProvider = actionBindingContext.MetadataProvider,
HttpContext = actionBindingContext.ActionContext.HttpContext,
ValueProvider = actionBindingContext.ValueProvider,
ModelBinder = bindingContext.ModelBinder,
ValidatorProvider = bindingContext.ValidatorProvider,
MetadataProvider = _modelMetadataProvider,
HttpContext = actionContext.HttpContext,
ValueProvider = bindingContext.ValueProvider,
};
foreach (var parameter in parameterMetadata)
{
var parameterType = parameter.ModelType;
var modelBindingContext = GetModelBindingContext(parameter, actionBindingContext, operationBindingContext);
if (await actionBindingContext.ModelBinder.BindModelAsync(modelBindingContext))
var modelBindingContext = GetModelBindingContext(parameter, actionContext, operationBindingContext);
if (await bindingContext.ModelBinder.BindModelAsync(modelBindingContext) &&
modelBindingContext.IsModelSet)
{
arguments[parameter.PropertyName] = modelBindingContext.Model;
}
@ -97,18 +98,18 @@ namespace Microsoft.AspNet.Mvc
internal static ModelBindingContext GetModelBindingContext(
ModelMetadata modelMetadata,
ActionBindingContext actionBindingContext,
ActionContext actionContext,
OperationBindingContext operationBindingContext)
{
var modelBindingContext = new ModelBindingContext
{
ModelName = modelMetadata.BinderModelName ?? modelMetadata.PropertyName,
ModelMetadata = modelMetadata,
ModelState = actionBindingContext.ActionContext.ModelState,
ModelState = actionContext.ModelState,
// Fallback only if there is no explicit model name set.
FallbackToEmptyPrefix = modelMetadata.BinderModelName == null,
ValueProvider = actionBindingContext.ValueProvider,
ValueProvider = operationBindingContext.ValueProvider,
OperationBindingContext = operationBindingContext,
};

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNet.Mvc
public class DefaultControllerActivator : IControllerActivator
{
private readonly Func<Type, PropertyActivator<ActionContext>[]> _getPropertiesToActivate;
private readonly IReadOnlyDictionary<Type, Func<ActionContext, object>> _valueAccessorLookup;
private readonly IDictionary<Type, Func<ActionContext, object>> _valueAccessorLookup;
private readonly ConcurrentDictionary<Type, PropertyActivator<ActionContext>[]> _injectActions;
/// <summary>
@ -39,7 +39,7 @@ namespace Microsoft.AspNet.Mvc
/// </summary>
/// <param name="controller">The controller to activate.</param>
/// <param name="context">The context of the executing action.</param>
public void Activate([NotNull] object controller, [NotNull] ActionContext context)
public virtual void Activate([NotNull] object controller, [NotNull] ActionContext context)
{
var controllerType = controller.GetType();
var controllerTypeInfo = controllerType.GetTypeInfo();
@ -58,7 +58,7 @@ namespace Microsoft.AspNet.Mvc
}
}
protected virtual IReadOnlyDictionary<Type, Func<ActionContext, object>> CreateValueAccessorLookup()
protected virtual IDictionary<Type, Func<ActionContext, object>> CreateValueAccessorLookup()
{
var dictionary = new Dictionary<Type, Func<ActionContext, object>>
{
@ -75,6 +75,15 @@ namespace Microsoft.AspNet.Mvc
serviceProvider.GetRequiredService<IModelMetadataProvider>(),
context.ModelState);
}
},
{
typeof(ActionBindingContext),
(context) =>
{
var serviceProvider = context.HttpContext.RequestServices;
var accessor = serviceProvider.GetRequiredService<IScopedInstance<ActionBindingContext>>();
return accessor.Value;
}
}
};
return dictionary;

View File

@ -30,14 +30,13 @@ namespace Microsoft.AspNet.Mvc
throw new ArgumentException(
Resources.FormatActionDescriptorMustBeBasedOnControllerAction(
typeof(ControllerActionDescriptor)),
"actionContext");
nameof(actionContext));
}
var controller = _typeActivator.CreateInstance(
_serviceProvider,
actionDescriptor.ControllerTypeInfo.AsType());
actionContext.Controller = controller;
_controllerActivator.Activate(controller, actionContext);
return controller;

View File

@ -17,7 +17,8 @@ namespace Microsoft.AspNet.Mvc
public class DefaultPropertyBindingPredicateProvider<TModel> : IPropertyBindingPredicateProvider
where TModel : class
{
private static readonly Func<ModelBindingContext, string, bool> _defaultFilter = (context, propertyName) => true;
private static readonly Func<ModelBindingContext, string, bool> _defaultFilter =
(context, propertyName) => true;
/// <summary>
/// The prefix which is used while generating the property filter.
@ -57,7 +58,8 @@ namespace Microsoft.AspNet.Mvc
}
}
private Func<ModelBindingContext, string, bool> GetPredicateFromExpression(IEnumerable<Expression<Func<TModel, object>>> includeExpressions)
private Func<ModelBindingContext, string, bool> GetPredicateFromExpression(
IEnumerable<Expression<Func<TModel, object>>> includeExpressions)
{
var expression = ModelBindingHelper.GetIncludePredicateExpression(Prefix, includeExpressions.ToArray());
return expression.Compile();

View File

@ -2,28 +2,38 @@
// Licensed 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.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Routing;
namespace Microsoft.AspNet.Mvc.Description
{
/// <summary>
/// A metadata description of an input to an API.
/// </summary>
public class ApiParameterDescription
{
public bool IsOptional { get; set; }
/// <summary>
/// Gets or sets the <see cref="ModelMetadata"/>.
/// </summary>
public ModelMetadata ModelMetadata { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; }
public ParameterDescriptor ParameterDescriptor { get; set; }
/// <summary>
/// Gets or sets the <see cref="ApiParameterRouteInfo"/>.
/// </summary>
public ApiParameterRouteInfo RouteInfo { get; set; }
/// <summary>
/// Gets or sets the <see cref="ApiParameterSource"/>.
/// </summary>
public ApiParameterSource Source { get; set; }
public IEnumerable<IRouteConstraint> Constraints { get; set; }
public object DefaultValue { get; set; }
/// <summary>
/// Gets or sets the parameter type.
/// </summary>
public Type Type { get; set; }
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic;
using Microsoft.AspNet.Routing;
namespace Microsoft.AspNet.Mvc.Description
{
/// <summary>
/// A metadata description of routing information for an <see cref="ApiParameterDescription"/>.
/// </summary>
public class ApiParameterRouteInfo
{
/// <summary>
/// Gets or sets the set of <see cref="IRouteConstraint"/> objects for the parameter.
/// </summary>
/// <remarks>
/// Route constraints are only applied when a value is bound from a URL's path. See
/// <see cref="ApiParameterDescription.Source"/> for the data source considered.
/// </remarks>
public IEnumerable<IRouteConstraint> Constraints { get; set; }
/// <summary>
/// Gets or sets the default value for the parameter.
/// </summary>
public object DefaultValue { get; set; }
/// <summary>
/// Gets a value indicating whether not a parameter is considered optional by routing.
/// </summary>
/// <remarks>
/// An optional parameter is considered optional by the routing system. This does not imply
/// that the parameter is considered optional by the action.
///
/// If the parameter uses <see cref="ApiParameterSource.ModelBinding"/> for the value of
/// <see cref="ApiParameterDescription.Source"/> then the value may also come from the
/// URL query string or form data.
/// </remarks>
public bool IsOptional { get; set; }
}
}

View File

@ -1,13 +1,130 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Diagnostics;
using Microsoft.AspNet.Mvc.Core;
namespace Microsoft.AspNet.Mvc.Description
{
// This is a placeholder - see #886
public enum ApiParameterSource
/// <summary>
/// A metadata description of the source of an <see cref="ApiParameterDescription"/> for an HTTP request.
/// </summary>
[DebuggerDisplay("Source: {DisplayName}")]
public class ApiParameterSource : IEquatable<ApiParameterSource>
{
Body,
Query,
Path
/// <summary>
/// An <see cref="ApiParameterSource"/> for the request body.
/// </summary>
public static readonly ApiParameterSource Body = new ApiParameterSource(
"Body",
Resources.ApiParameterSource_Body);
/// <summary>
/// An <see cref="ApiParameterSource"/> for a custom model binder (unknown data source).
/// </summary>
public static readonly ApiParameterSource Custom = new ApiParameterSource(
"Custom",
Resources.ApiParameterSource_Custom);
/// <summary>
/// An <see cref="ApiParameterSource"/> for the request form-data.
/// </summary>
public static readonly ApiParameterSource Form = new ApiParameterSource(
"Form",
Resources.ApiParameterSource_Form);
/// <summary>
/// An <see cref="ApiParameterSource"/> for the request headers.
/// </summary>
public static readonly ApiParameterSource Header = new ApiParameterSource(
"Header",
Resources.ApiParameterSource_Header);
/// <summary>
/// An <see cref="ApiParameterSource"/> for a parameter that should be hidden. Used when
/// a parameter cannot be set with user input.
/// </summary>
public static readonly ApiParameterSource Hidden = new ApiParameterSource(
"Hidden",
Resources.ApiParameterSource_Hidden);
/// <summary>
/// An <see cref="ApiParameterSource"/> for model binding. Includes form-data, query-string
/// and headers from the request.
/// </summary>
public static readonly ApiParameterSource ModelBinding = new ApiParameterSource(
"ModelBinding",
Resources.ApiParameterSource_ModelBinding);
/// <summary>
/// An <see cref="ApiParameterSource"/> for the request url path.
/// </summary>
public static readonly ApiParameterSource Path = new ApiParameterSource(
"Path",
Resources.ApiParameterSource_Path);
/// <summary>
/// An <see cref="ApiParameterSource"/> for the request query-string.
/// </summary>
public static readonly ApiParameterSource Query = new ApiParameterSource(
"Query",
Resources.ApiParameterSource_Query);
/// <summary>
/// Creates a new <see cref="ApiParameterSource"/>.
/// </summary>
/// <param name="id">The id. Used for comparison.</param>
/// <param name="displayName"> The display name.</param>
public ApiParameterSource([NotNull] string id, string displayName)
{
Id = id;
DisplayName = displayName;
}
/// <summary>
/// Gets the display name.
/// </summary>
public string DisplayName { get; }
/// <summary>
/// Gets the id.
/// </summary>
public string Id { get; }
/// <inheritdoc />
public bool Equals(ApiParameterSource other)
{
return other == null ? false : string.Equals(other.Id, Id, StringComparison.Ordinal);
}
/// <inheritdoc />
public override bool Equals(object obj)
{
return Equals(obj as ApiParameterSource);
}
/// <inheritdoc />
public override int GetHashCode()
{
return Id.GetHashCode();
}
/// <inheritdoc />
public static bool operator ==(ApiParameterSource s1, ApiParameterSource s2)
{
if (object.ReferenceEquals(s1, null))
{
return object.ReferenceEquals(s2, null); ;
}
return s1.Equals(s2);
}
/// <inheritdoc />
public static bool operator !=(ApiParameterSource s1, ApiParameterSource s2)
{
return !(s1 == s2);
}
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Description
{

View File

@ -3,13 +3,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Routing;
using Microsoft.AspNet.Routing.Template;
using Microsoft.Net.Http.Headers;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc.Description
@ -35,8 +34,8 @@ namespace Microsoft.AspNet.Mvc.Description
IModelMetadataProvider modelMetadataProvider)
{
_formattersProvider = formattersProvider;
_modelMetadataProvider = modelMetadataProvider;
_constraintResolver = constraintResolver;
_modelMetadataProvider = modelMetadataProvider;
}
/// <inheritdoc />
@ -45,7 +44,6 @@ namespace Microsoft.AspNet.Mvc.Description
get { return DefaultOrder.DefaultFrameworkSortOrder; }
}
/// <inheritdoc />
public void Invoke(ApiDescriptionProviderContext context, Action callNext)
{
@ -82,7 +80,8 @@ namespace Microsoft.AspNet.Mvc.Description
var templateParameters = parsedTemplate?.Parameters?.ToList() ?? new List<TemplatePart>();
GetParameters(apiDescription, action.Parameters, templateParameters);
var parameterContext = new ApiParameterContext(_modelMetadataProvider, action, templateParameters);
apiDescription.ParameterDescriptions.AddRange(GetParameters(parameterContext));
var responseMetadataAttributes = GetResponseMetadataAttributes(action);
@ -125,42 +124,90 @@ namespace Microsoft.AspNet.Mvc.Description
return apiDescription;
}
private void GetParameters(
ApiDescription apiDescription,
IList<ParameterDescriptor> parameterDescriptors,
IList<TemplatePart> templateParameters)
private IList<ApiParameterDescription> GetParameters(ApiParameterContext context)
{
if (parameterDescriptors != null)
// First, get parameters from the model-binding/parameter-binding side of the world.
if (context.ActionDescriptor.Parameters != null)
{
foreach (var parameter in parameterDescriptors)
foreach (var actionParameter in context.ActionDescriptor.Parameters)
{
// Process together parameters that appear on the path template and on the
// action descriptor and do not come from the body.
TemplatePart templateParameter = null;
if (parameter.BinderMetadata as IFormatterBinderMetadata == null)
{
templateParameter = templateParameters
.FirstOrDefault(p => p.Name.Equals(parameter.Name, StringComparison.OrdinalIgnoreCase));
var visitor = new PseudoModelBindingVisitor(context, actionParameter);
visitor.WalkParameter();
}
}
if (templateParameter != null)
for (var i = context.Results.Count - 1; i >= 0; i--)
{
// Remove any 'hidden' parameters. These are things that can't come from user input,
// so they aren't worth showing.
if (context.Results[i].Source == ApiParameterSource.Hidden)
{
context.Results.RemoveAt(i);
}
}
// Next, we want to join up any route parameters with those discovered from the action's parameters.
var routeParameters = new Dictionary<string, ApiParameterRouteInfo>(StringComparer.OrdinalIgnoreCase);
foreach (var routeParameter in context.RouteParameters)
{
routeParameters.Add(routeParameter.Name, CreateRouteInfo(routeParameter));
}
foreach (var parameter in context.Results)
{
if (parameter.Source == ApiParameterSource.Path ||
parameter.Source == ApiParameterSource.ModelBinding ||
parameter.Source == ApiParameterSource.Custom)
{
ApiParameterRouteInfo routeInfo;
if (routeParameters.TryGetValue(parameter.Name, out routeInfo))
{
parameter.RouteInfo = routeInfo;
routeParameters.Remove(parameter.Name);
if (parameter.Source == ApiParameterSource.ModelBinding &&
!parameter.RouteInfo.IsOptional)
{
templateParameters.Remove(templateParameter);
// If we didn't see any information about the parameter, but we have
// a route parameter that matches, let's switch it to path.
parameter.Source = ApiParameterSource.Path;
}
}
apiDescription.ParameterDescriptions.Add(GetParameter(parameter, templateParameter));
}
}
if (templateParameters.Count > 0)
// Lastly, create a parameter representation for each route parameter that did not find
// a partner.
foreach (var routeParameter in routeParameters)
{
// Process parameters that only appear on the path template if any.
foreach (var templateParameter in templateParameters)
context.Results.Add(new ApiParameterDescription()
{
var parameterDescription = GetParameter(parameterDescriptor: null, templateParameter: templateParameter);
apiDescription.ParameterDescriptions.Add(parameterDescription);
Name = routeParameter.Key,
RouteInfo = routeParameter.Value,
Source = ApiParameterSource.Path,
});
}
return context.Results;
}
private ApiParameterRouteInfo CreateRouteInfo(TemplatePart routeParameter)
{
var constraints = new List<IRouteConstraint>();
if (routeParameter.InlineConstraints != null)
{
foreach (var constraint in routeParameter.InlineConstraints)
{
constraints.Add(_constraintResolver.ResolveConstraint(constraint.Constraint));
}
}
return new ApiParameterRouteInfo()
{
Constraints = constraints,
DefaultValue = routeParameter.DefaultValue,
IsOptional = routeParameter.IsOptional || routeParameter.DefaultValue != null,
};
}
private IEnumerable<string> GetHttpMethods(ControllerActionDescriptor action)
@ -216,118 +263,6 @@ namespace Microsoft.AspNet.Mvc.Description
return string.Join("/", segments);
}
private ApiParameterDescription GetParameter(
ParameterDescriptor parameterDescriptor,
TemplatePart templateParameter)
{
// This is a placeholder based on currently available functionality for parameters. See #886.
ApiParameterDescription parameterDescription = null;
if (templateParameter != null && parameterDescriptor == null)
{
// The parameter is part of the route template but not part of the ActionDescriptor.
// For now if a parameter is part of the template we will asume its value comes from the path.
// We will be more accurate when we implement #886.
parameterDescription = CreateParameterFromTemplate(templateParameter);
}
else if (templateParameter != null && parameterDescriptor != null)
{
// The parameter is part of the route template and part of the ActionDescriptor.
parameterDescription = CreateParameterFromTemplateAndParameterDescriptor(
templateParameter,
parameterDescriptor);
}
else if (templateParameter == null && parameterDescriptor != null)
{
// The parameter is part of the ActionDescriptor but is not part of the route template.
parameterDescription = CreateParameterFromParameterDescriptor(parameterDescriptor);
}
else
{
// We will never call this method with templateParameter == null && parameterDescriptor == null
Debug.Assert(parameterDescriptor != null);
}
if (parameterDescription.Type != null)
{
parameterDescription.ModelMetadata = _modelMetadataProvider.GetMetadataForType(
modelAccessor: null,
modelType: parameterDescription.Type);
}
return parameterDescription;
}
private static ApiParameterDescription CreateParameterFromParameterDescriptor(ParameterDescriptor parameter)
{
var resourceParameter = new ApiParameterDescription
{
IsOptional = parameter.IsOptional,
Name = parameter.Name,
ParameterDescriptor = parameter,
Type = parameter.ParameterType,
};
if (parameter.BinderMetadata as IFormatterBinderMetadata != null)
{
resourceParameter.Source = ApiParameterSource.Body;
}
else
{
resourceParameter.Source = ApiParameterSource.Query;
}
return resourceParameter;
}
private ApiParameterDescription CreateParameterFromTemplateAndParameterDescriptor(
TemplatePart templateParameter,
ParameterDescriptor parameter)
{
var resourceParameter = new ApiParameterDescription
{
Source = ApiParameterSource.Path,
IsOptional = parameter.IsOptional && IsOptionalParameter(templateParameter),
Name = parameter.Name,
ParameterDescriptor = parameter,
Constraints = GetConstraints(_constraintResolver, templateParameter.InlineConstraints),
DefaultValue = templateParameter.DefaultValue,
Type = parameter.ParameterType,
};
return resourceParameter;
}
private static IEnumerable<IRouteConstraint> GetConstraints(
IInlineConstraintResolver constraintResolver,
IEnumerable<InlineConstraint> constraints)
{
return
constraints
.Select(c => constraintResolver.ResolveConstraint(c.Constraint))
.Where(c => c != null)
.ToArray();
}
private static bool IsOptionalParameter(TemplatePart templateParameter)
{
return templateParameter.IsOptional || templateParameter.DefaultValue != null;
}
private ApiParameterDescription CreateParameterFromTemplate(TemplatePart templateParameter)
{
return new ApiParameterDescription
{
Source = ApiParameterSource.Path,
IsOptional = IsOptionalParameter(templateParameter),
Name = templateParameter.Name,
ParameterDescriptor = null,
Constraints = GetConstraints(_constraintResolver, templateParameter.InlineConstraints),
DefaultValue = templateParameter.DefaultValue,
};
}
private IReadOnlyList<ApiResponseFormat> GetResponseFormats(
ControllerActionDescriptor action,
IApiResponseMetadataProvider[] responseMetadataAttributes,
@ -443,7 +378,7 @@ namespace Microsoft.AspNet.Mvc.Description
}
// This technique for enumerating filters will intentionally ignore any filter that is an IFilterFactory
// for a filter that implements IApiResponseMetadataProvider.
// while searching for a filter that implements IApiResponseMetadataProvider.
//
// The workaround for that is to implement the metadata interface on the IFilterFactory.
return action.FilterDescriptors
@ -451,5 +386,333 @@ namespace Microsoft.AspNet.Mvc.Description
.OfType<IApiResponseMetadataProvider>()
.ToArray();
}
private class ApiParameterContext
{
public ApiParameterContext(
IModelMetadataProvider metadataProvider,
ControllerActionDescriptor actionDescriptor,
IReadOnlyList<TemplatePart> routeParameters)
{
MetadataProvider = metadataProvider;
ActionDescriptor = actionDescriptor;
RouteParameters = routeParameters;
Results = new List<ApiParameterDescription>();
}
public ControllerActionDescriptor ActionDescriptor { get; }
public IModelMetadataProvider MetadataProvider { get; }
public IList<ApiParameterDescription> Results { get; }
public IReadOnlyList<TemplatePart> RouteParameters { get; }
}
private class PseudoModelBindingVisitor
{
public PseudoModelBindingVisitor(ApiParameterContext context, ParameterDescriptor parameter)
{
Context = context;
Parameter = parameter;
Visited = new HashSet<PropertyKey>();
}
public ApiParameterContext Context { get; }
public ParameterDescriptor Parameter { get; }
// Avoid infinite recursion by tracking properties.
private HashSet<PropertyKey> Visited { get; }
public void WalkParameter()
{
var modelMetadata = Context.MetadataProvider.GetMetadataForParameter(
modelAccessor: null,
methodInfo: Context.ActionDescriptor.MethodInfo,
parameterName: Parameter.Name);
var binderMetadata = Parameter.BinderMetadata;
if (binderMetadata != null)
{
modelMetadata.BinderMetadata = binderMetadata;
}
var nameProvider = binderMetadata as IModelNameProvider;
if (nameProvider != null && nameProvider.Name != null)
{
modelMetadata.BinderModelName = nameProvider.Name;
}
// Attempt to find a binding source for the parameter
//
// The default is ModelBinding (aka all default value providers)
var source = ApiParameterSource.ModelBinding;
if (!Visit(modelMetadata, source, containerName: string.Empty))
{
// If we get here, then it means we didn't find a match for any of the model. This means that it's
// likely 'model-bound' in the traditional MVC sense (formdata + query string + route data) and
// doesn't use any IBinderMetadata.
//
// Add a single 'default' parameter description for the model.
Context.Results.Add(CreateResult(modelMetadata, source, containerName: string.Empty));
}
}
/// <summary>
/// Visits a node in a model, and attempts to create <see cref="ApiParameterDescription"/> for any
/// model properties where we can definitely compute an answer.
/// </summary>
/// <param name="modelMetadata">The metadata for the model.</param>
/// <param name="ambientSource">The <see cref="ApiParameterSource"/> from the ambient context.</param>
/// <param name="containerName">The current name prefix (to prepend to property names).</param>
/// <returns>
/// <c>true</c> if the set of <see cref="ApiParameterDescription"/> objects were created for the model.
/// <c>false</c> if no <see cref="ApiParameterDescription"/> objects were created for the model.
/// </returns>
/// <remarks>
/// Its the reponsibility of this method to create a parameter description for ALL of the current model
/// or NONE of it. If a parameter description is created for ANY sub-properties of the model, then a parameter
/// description will be created for ALL of them.
/// </remarks>
private bool Visit(ModelMetadata modelMetadata, ApiParameterSource ambientSource, string containerName)
{
ApiParameterSource source;
if (GetSource(modelMetadata, out source))
{
// We have a definite answer for this model. This is a greedy source like
// [FromBody] so there's no need to consider properties.
Context.Results.Add(CreateResult(modelMetadata, source, containerName));
return true;
}
// For any property which is a leaf node, we don't want to keep traversing:
//
// 1) Collections - while it's possible to have binder attributes on the inside of a collection,
// it hardly seems useful, and would result in some very wierd binding.
//
// 2) Simple Types - These are generally part of the .net framework - primitives, or types which have a
// type converter from string.
//
// 3) Types with no properties. Obviously nothing to explore there.
//
if (modelMetadata.IsCollectionType ||
!modelMetadata.IsComplexType ||
!modelMetadata.Properties.Any())
{
if (source == null || source == ambientSource)
{
// If it's a leaf node, and we have no new source then we don't know how to bind this.
// Return without creating any parameters, so that this can be included in the parent model.
return false;
}
else
{
// We found a new source, and this model has no properties. This is probabaly
// a simple type with an attribute like [FromQuery].
Context.Results.Add(CreateResult(modelMetadata, source, containerName));
return true;
}
}
// This will come from composite model binding - so investigate what's going on with each property.
//
// Basically once we find something that we know how to bind, we want to treat all properties at that
// level (and higher levels) as separate parameters.
//
// Ex:
//
// public IActionResult PlaceOrder(OrderDTO order) {...}
//
// public class OrderDTO
// {
// public int AccountId { get; set; }
//
// [FromBody]
// public Order { get; set; }
// }
//
// This should result in two parameters:
//
// AccountId - source: Any
// Order - source: Body
//
var propertyCount = 0;
var unboundProperties = new HashSet<ModelMetadata>();
// We don't want to append the **parameter** name when building a model name.
var newContainerName = containerName;
if (modelMetadata.ContainerType != null)
{
newContainerName = GetName(containerName, modelMetadata);
}
foreach (var propertyMetadata in modelMetadata.Properties)
{
propertyCount++;
var key = new PropertyKey(propertyMetadata, source);
if (Visited.Add(key))
{
if (!Visit(propertyMetadata, source ?? ambientSource, newContainerName))
{
unboundProperties.Add(propertyMetadata);
}
}
else
{
unboundProperties.Add(propertyMetadata);
}
}
if (unboundProperties.Count == propertyCount)
{
if (source == null || source == ambientSource)
{
// No properties were bound and we didn't find a new source, let the caller handle it.
return false;
}
else
{
// We found a new source, and didn't create a result for any of the properties yet,
// so create a result for the current object.
Context.Results.Add(CreateResult(modelMetadata, source, containerName));
return true;
}
}
else
{
// This model was only partially bound, so create a result for all the other properties
foreach (var property in unboundProperties)
{
// Create a 'default' description for each property
Context.Results.Add(CreateResult(property, source ?? ambientSource, newContainerName));
}
return true;
}
}
private ApiParameterDescription CreateResult(
ModelMetadata metadata,
ApiParameterSource source,
string containerName)
{
return new ApiParameterDescription()
{
ModelMetadata = metadata,
Name = GetName(containerName, metadata),
Source = source,
Type = metadata.ModelType,
};
}
private static string GetName(string containerName, ModelMetadata metadata)
{
if (!string.IsNullOrEmpty(metadata.BinderModelName))
{
// Name was explicitly provided
return metadata.BinderModelName;
}
else
{
return ModelBindingHelper.CreatePropertyModelName(containerName, metadata.PropertyName);
}
}
// This isn't extensible right now.
//
// Returns true if the source is greedy (means to stop exploring the model)
// Returns false if the source in unknown or known but not greedy (like [FromQuery])
private static bool GetSource(ModelMetadata metadata, out ApiParameterSource source)
{
if (metadata.BinderMetadata == null)
{
// There's nothing we can figure out.
source = null;
return false;
}
if (metadata.BinderMetadata is IFormatterBinderMetadata)
{
source = ApiParameterSource.Body;
return true;
}
else if (metadata.BinderMetadata is IHeaderBinderMetadata)
{
source = ApiParameterSource.Header;
return true;
}
else if (metadata.BinderMetadata is IServiceActivatorBinderMetadata)
{
source = ApiParameterSource.Hidden;
return true;
}
else if (metadata.BinderMetadata is IRouteDataValueProviderMetadata)
{
source = ApiParameterSource.Path;
return false;
}
else if (metadata.BinderMetadata is IQueryValueProviderMetadata)
{
source = ApiParameterSource.Query;
return false;
}
else if (metadata.BinderMetadata is IFormDataValueProviderMetadata)
{
source = ApiParameterSource.Form;
return false;
}
var binderTypeMetadata = metadata.BinderMetadata as IBinderTypeProviderMetadata;
if (binderTypeMetadata != null && binderTypeMetadata.BinderType != null)
{
// This provides it's own model binder, so we can't really make a good
// estimate of where it comes from.
source = ApiParameterSource.Custom;
return true;
}
// We're out of cases we know how to handle.
source = null;
return false;
}
private struct PropertyKey
{
public readonly Type ContainerType;
public readonly string PropertyName;
public readonly ApiParameterSource Source;
public PropertyKey(ModelMetadata metadata, ApiParameterSource source)
{
ContainerType = metadata.ContainerType;
PropertyName = metadata.PropertyName;
Source = source;
}
}
private class PropertyKeyEqualityComparer : IEqualityComparer<PropertyKey>
{
public bool Equals(PropertyKey x, PropertyKey y)
{
return
x.ContainerType == y.ContainerType &&
x.PropertyName == y.PropertyName &&
x.Source == y.Source;
}
public int GetHashCode(PropertyKey obj)
{
return obj.ContainerType.GetHashCode() ^ obj.PropertyName.GetHashCode() ^ obj.Source.GetHashCode();
}
}
}
}
}

View File

@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.HeaderValueAbstractions;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc.Description
{

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc
@ -15,14 +16,22 @@ namespace Microsoft.AspNet.Mvc
public abstract class FilterActionInvoker : IActionInvoker
{
private readonly INestedProviderManager<FilterProviderContext> _filterProvider;
private readonly IInputFormattersProvider _inputFormatterProvider;
private readonly IModelBinderProvider _modelBinderProvider;
private readonly IModelValidatorProviderProvider _modelValidatorProviderProvider;
private readonly IValueProviderFactoryProvider _valueProviderFactoryProvider;
private readonly IScopedInstance<ActionBindingContext> _actionBindingContextAccessor;
private IFilter[] _filters;
private FilterCursor _cursor;
private ExceptionContext _exceptionContext;
private AuthorizationContext _authorizationContext;
private ResourceExecutingContext _resourceExecutingContext;
private ResourceExecutedContext _resourceExecutedContext;
private ExceptionContext _exceptionContext;
private ActionExecutingContext _actionExecutingContext;
private ActionExecutedContext _actionExecutedContext;
@ -31,17 +40,57 @@ namespace Microsoft.AspNet.Mvc
public FilterActionInvoker(
[NotNull] ActionContext actionContext,
[NotNull] INestedProviderManager<FilterProviderContext> filterProvider)
[NotNull] INestedProviderManager<FilterProviderContext> filterProvider,
[NotNull] IInputFormattersProvider inputFormatterProvider,
[NotNull] IModelBinderProvider modelBinderProvider,
[NotNull] IModelValidatorProviderProvider modelValidatorProviderProvider,
[NotNull] IValueProviderFactoryProvider valueProviderFactoryProvider,
[NotNull] IScopedInstance<ActionBindingContext> actionBindingContextAccessor)
{
ActionContext = actionContext;
_filterProvider = filterProvider;
_inputFormatterProvider = inputFormatterProvider;
_modelBinderProvider = modelBinderProvider;
_modelValidatorProviderProvider = modelValidatorProviderProvider;
_valueProviderFactoryProvider = valueProviderFactoryProvider;
_actionBindingContextAccessor = actionBindingContextAccessor;
}
protected ActionContext ActionContext { get; private set; }
protected ActionBindingContext ActionBindingContext
{
get
{
return _actionBindingContextAccessor.Value;
}
private set
{
_actionBindingContextAccessor.Value = value;
}
}
protected object Instance { get; private set; }
/// <summary>
/// Called to create an instance of an object which will act as the reciever of the action invocation.
/// </summary>
/// <returns>The constructed instance or <c>null</c>.</returns>
protected abstract object CreateInstance();
/// <summary>
/// Called to create an instance of an object which will act as the reciever of the action invocation.
/// </summary>
/// <param name="instance">The instance to release.</param>
/// <remarks>This method will not be called if <see cref="CreateInstance"/> returns <c>null</c>.</remarks>
protected abstract void ReleaseInstance(object instance);
protected abstract Task<IActionResult> InvokeActionAsync(ActionExecutingContext actionExecutingContext);
protected abstract Task<IDictionary<string, object>> GetActionArgumentsAsync([NotNull] ActionContext context);
protected abstract Task<IDictionary<string, object>> GetActionArgumentsAsync(
[NotNull] ActionContext context,
[NotNull] ActionBindingContext bindingContext);
public virtual async Task InvokeAsync()
{
@ -59,38 +108,35 @@ namespace Microsoft.AspNet.Mvc
return;
}
// >> ExceptionFilters >> ActionFilters >> Action
await InvokeAllExceptionFiltersAsync();
// If Exception Filters provide a result, it's a short-circuit due to an exception.
// We don't execute Result Filters around the result.
Debug.Assert(_exceptionContext != null);
if (_exceptionContext.Result != null)
try
{
await _exceptionContext.Result.ExecuteResultAsync(ActionContext);
await InvokeAllResourceFiltersAsync();
}
else if (_exceptionContext.Exception != null)
finally
{
// If we get here, this means that we have an unhandled exception
if (_exceptionContext.ExceptionDispatchInfo != null)
// Release the instance after all filters have run. We don't need to surround
// Authorizations filters because the instance will be created much later than
// that.
if (Instance != null)
{
_exceptionContext.ExceptionDispatchInfo.Throw();
ReleaseInstance(Instance);
}
}
// We've reached the end of resource filters. If there's an unhandled exception on the context then
// it should be thrown and middleware has a chance to handle it.
Debug.Assert(_resourceExecutedContext != null);
if (_resourceExecutedContext.Exception != null && !_resourceExecutedContext.ExceptionHandled)
{
if (_resourceExecutedContext.ExceptionDispatchInfo == null)
{
throw _resourceExecutedContext.Exception;
}
else
{
throw _exceptionContext.Exception;
_resourceExecutedContext.ExceptionDispatchInfo.Throw();
}
}
else
{
// We have a successful 'result' from the action or an Action Filter, so run
// Result Filters.
Debug.Assert(_actionExecutedContext != null);
var result = _actionExecutedContext.Result;
// >> ResultFilters >> (Result)
await InvokeAllResultFiltersAsync(result);
}
}
private IFilter[] GetFilters()
@ -146,6 +192,148 @@ namespace Microsoft.AspNet.Mvc
}
}
private async Task InvokeAllResourceFiltersAsync()
{
_cursor.SetStage(FilterStage.ResourceFilters);
var context = new ResourceExecutingContext(ActionContext, _filters);
context.InputFormatters = new List<IInputFormatter>(_inputFormatterProvider.InputFormatters);
context.ModelBinders = new List<IModelBinder>(_modelBinderProvider.ModelBinders);
context.ValidatorProviders = new List<IModelValidatorProvider>(
_modelValidatorProviderProvider.ModelValidatorProviders);
context.ValueProviderFactories = new List<IValueProviderFactory>(
_valueProviderFactoryProvider.ValueProviderFactories);
_resourceExecutingContext = context;
await InvokeResourceFilterAsync();
}
private async Task<ResourceExecutedContext> InvokeResourceFilterAsync()
{
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);
}
var item = _cursor.GetNextFilter<IResourceFilter, IAsyncResourceFilter>();
try
{
if (item.FilterAsync != null)
{
await item.FilterAsync.OnResourceExecutionAsync(
_resourceExecutingContext,
InvokeResourceFilterAsync);
if (_resourceExecutedContext == null)
{
// If we get here then the filter didn't call 'next' indicating a short circuit
if (_resourceExecutingContext.Result != null)
{
await _resourceExecutingContext.Result.ExecuteResultAsync(ActionContext);
}
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
{
Canceled = true,
Result = _resourceExecutingContext.Result,
};
}
}
else if (item.Filter != null)
{
item.Filter.OnResourceExecuting(_resourceExecutingContext);
if (_resourceExecutingContext.Result != null)
{
// Short-circuited by setting a result.
await _resourceExecutingContext.Result.ExecuteResultAsync(ActionContext);
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
{
Canceled = true,
Result = _resourceExecutingContext.Result,
};
}
else
{
item.Filter.OnResourceExecuted(await InvokeResourceFilterAsync());
}
}
else
{
// We've reached the end of resource filters, so move on to exception filters.
// >> ExceptionFilters >> Model Binding >> ActionFilters >> Action
await InvokeAllExceptionFiltersAsync();
// If Exception Filters provide a result, it's a short-circuit due to an exception.
// We don't execute Result Filters around the result.
Debug.Assert(_exceptionContext != null);
if (_exceptionContext.Result != null)
{
// This means that exception filters returned a result to 'handle' an error.
// We're not interested in seeing the exception details since it was handled.
await _exceptionContext.Result.ExecuteResultAsync(ActionContext);
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
{
Result = _exceptionContext.Result,
};
}
else if (_exceptionContext.Exception != null)
{
// If we get here, this means that we have an unhandled exception.
// Exception filted didn't handle this, so send it on to resource filters.
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters);
// Preserve the stack trace if possible.
_resourceExecutedContext.Exception = _exceptionContext.Exception;
if (_exceptionContext.ExceptionDispatchInfo != null)
{
_resourceExecutedContext.ExceptionDispatchInfo = _exceptionContext.ExceptionDispatchInfo;
}
}
else
{
// We have a successful 'result' from the action or an Action Filter, so run
// Result Filters.
Debug.Assert(_actionExecutedContext != null);
var result = _actionExecutedContext.Result;
// >> ResultFilters >> (Result)
await InvokeAllResultFiltersAsync(result);
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
{
Result = _resultExecutedContext.Result,
};
}
}
}
catch (Exception exception)
{
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
{
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception)
};
}
Debug.Assert(_resourceExecutedContext != null);
return _resourceExecutedContext;
}
private async Task InvokeAllExceptionFiltersAsync()
{
_cursor.SetStage(FilterStage.ExceptionFilters);
@ -219,8 +407,33 @@ namespace Microsoft.AspNet.Mvc
private async Task InvokeAllActionFiltersAsync()
{
_cursor.SetStage(FilterStage.ActionFilters);
var arguments = await GetActionArgumentsAsync(ActionContext);
_actionExecutingContext = new ActionExecutingContext(ActionContext, _filters, arguments);
Debug.Assert(_resourceExecutingContext != null);
ActionBindingContext = new ActionBindingContext();
ActionBindingContext.InputFormatters = _resourceExecutingContext.InputFormatters;
ActionBindingContext.ModelBinder = new CompositeModelBinder(_resourceExecutingContext.ModelBinders);
ActionBindingContext.ValidatorProvider = new CompositeModelValidatorProvider(
_resourceExecutingContext.ValidatorProviders);
var valueProviderFactoryContext = new ValueProviderFactoryContext(
ActionContext.HttpContext,
ActionContext.RouteData.Values);
ActionBindingContext.ValueProvider = CompositeValueProvider.Create(
_resourceExecutingContext.ValueProviderFactories,
valueProviderFactoryContext);
Instance = CreateInstance();
var arguments = await GetActionArgumentsAsync(ActionContext, ActionBindingContext);
_actionExecutingContext = new ActionExecutingContext(
ActionContext,
_filters,
arguments,
Instance);
await InvokeActionFilterAsync();
}
@ -232,7 +445,7 @@ namespace Microsoft.AspNet.Mvc
// 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,
"Result",
nameof(ActionExecutingContext.Result),
typeof(ActionExecutingContext).Name,
typeof(ActionExecutionDelegate).Name);
@ -249,7 +462,10 @@ namespace Microsoft.AspNet.Mvc
if (_actionExecutedContext == null)
{
// If we get here then the filter didn't call 'next' indicating a short circuit
_actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters)
_actionExecutedContext = new ActionExecutedContext(
_actionExecutingContext,
_filters,
Instance)
{
Canceled = true,
Result = _actionExecutingContext.Result,
@ -263,7 +479,10 @@ namespace Microsoft.AspNet.Mvc
if (_actionExecutingContext.Result != null)
{
// Short-circuited by setting a result.
_actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters)
_actionExecutedContext = new ActionExecutedContext(
_actionExecutingContext,
_filters,
Instance)
{
Canceled = true,
Result = _actionExecutingContext.Result,
@ -277,7 +496,10 @@ namespace Microsoft.AspNet.Mvc
else
{
// All action filters have run, execute the action method.
_actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters)
_actionExecutedContext = new ActionExecutedContext(
_actionExecutingContext,
_filters,
Instance)
{
Result = await InvokeActionAsync(_actionExecutingContext),
};
@ -286,7 +508,10 @@ namespace Microsoft.AspNet.Mvc
catch (Exception exception)
{
// Exceptions thrown by the action method OR filters bubble back up through ActionExcecutedContext.
_actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters)
_actionExecutedContext = new ActionExecutedContext(
_actionExecutingContext,
_filters,
Instance)
{
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception)
};
@ -298,7 +523,7 @@ namespace Microsoft.AspNet.Mvc
{
_cursor.SetStage(FilterStage.ResultFilters);
_resultExecutingContext = new ResultExecutingContext(ActionContext, _filters, result);
_resultExecutingContext = new ResultExecutingContext(ActionContext, _filters, result, Instance);
await InvokeResultFilterAsync();
Debug.Assert(_resultExecutingContext != null);
@ -325,7 +550,7 @@ namespace Microsoft.AspNet.Mvc
// This is forbidden.
var message = Resources.FormatAsyncResultFilter_InvalidShortCircuit(
typeof(IAsyncResultFilter).Name,
"Cancel",
nameof(ResultExecutingContext.Cancel),
typeof(ResultExecutingContext).Name,
typeof(ResultExecutionDelegate).Name);
@ -345,7 +570,8 @@ namespace Microsoft.AspNet.Mvc
_resultExecutedContext = new ResultExecutedContext(
_resultExecutingContext,
_filters,
_resultExecutingContext.Result)
_resultExecutingContext.Result,
Instance)
{
Canceled = true,
};
@ -356,7 +582,8 @@ namespace Microsoft.AspNet.Mvc
_resultExecutedContext = new ResultExecutedContext(
_resultExecutingContext,
_filters,
_resultExecutingContext.Result)
_resultExecutingContext.Result,
Instance)
{
Canceled = true,
};
@ -372,7 +599,8 @@ namespace Microsoft.AspNet.Mvc
_resultExecutedContext = new ResultExecutedContext(
_resultExecutingContext,
_filters,
_resultExecutingContext.Result)
_resultExecutingContext.Result,
Instance)
{
Canceled = true,
};
@ -390,7 +618,8 @@ namespace Microsoft.AspNet.Mvc
_resultExecutedContext = new ResultExecutedContext(
_resultExecutingContext,
_filters,
_resultExecutingContext.Result);
_resultExecutingContext.Result,
Instance);
}
}
catch (Exception exception)
@ -398,7 +627,8 @@ namespace Microsoft.AspNet.Mvc
_resultExecutedContext = new ResultExecutedContext(
_resultExecutingContext,
_filters,
_resultExecutingContext.Result)
_resultExecutingContext.Result,
Instance)
{
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception)
};
@ -423,8 +653,9 @@ namespace Microsoft.AspNet.Mvc
private enum FilterStage
{
Undefined,
ExceptionFilters,
AuthorizationFilters,
ResourceFilters,
ExceptionFilters,
ActionFilters,
ActionMethod,
ResultFilters,

View File

@ -14,13 +14,17 @@ namespace Microsoft.AspNet.Mvc
public ActionExecutedContext(
[NotNull] ActionContext actionContext,
[NotNull] IList<IFilter> filters)
[NotNull] IList<IFilter> filters,
object controller)
: base(actionContext, filters)
{
Controller = controller;
}
public virtual bool Canceled { get; set; }
public virtual object Controller { get; }
public virtual Exception Exception
{
get

View File

@ -10,14 +10,18 @@ namespace Microsoft.AspNet.Mvc
public ActionExecutingContext(
[NotNull] ActionContext actionContext,
[NotNull] IList<IFilter> filters,
[NotNull] IDictionary<string, object> actionArguments)
[NotNull] IDictionary<string, object> actionArguments,
object controller)
: base(actionContext, filters)
{
ActionArguments = actionArguments;
Controller = controller;
}
public virtual IActionResult Result { get; set; }
public virtual IDictionary<string, object> ActionArguments { get; private set; }
public virtual IDictionary<string, object> ActionArguments { get; }
public virtual object Controller { get; }
}
}

View File

@ -2,9 +2,6 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Security;
@ -14,69 +11,74 @@ namespace Microsoft.AspNet.Mvc
{
public class AuthorizeAttribute : AuthorizationFilterAttribute
{
protected Claim[] _claims;
private string _roles;
private string[] _rolesSplit;
public AuthorizeAttribute()
public AuthorizeAttribute() { }
public AuthorizeAttribute(string policy)
{
_claims = new Claim[0];
Policy = policy;
}
public AuthorizeAttribute([NotNull]IEnumerable<Claim> claims)
{
_claims = claims.ToArray();
}
public string Policy { get; set; }
public AuthorizeAttribute(string claimType, string claimValue)
public string Roles
{
_claims = new[] { new Claim(claimType, claimValue) };
}
public AuthorizeAttribute(string claimType, string claimValue, params string[] otherClaimValues)
: this(claimType, claimValue)
{
if (otherClaimValues.Length > 0)
get { return _roles; }
set
{
_claims = _claims.Concat(otherClaimValues.Select(claim => new Claim(claimType, claim))).ToArray();
_roles = value;
if (string.IsNullOrWhiteSpace(_roles))
{
_rolesSplit = null;
}
else
{
_rolesSplit = _roles.Split(',');
}
}
}
public override async Task OnAuthorizationAsync([NotNull] AuthorizationContext context)
{
var httpContext = context.HttpContext;
var user = httpContext.User;
// when no claims are specified, we just need to ensure the user is authenticated
if (_claims.Length == 0)
// Allow Anonymous skips all authorization
if (HasAllowAnonymous(context))
{
var userIsAnonymous =
user == null ||
user.Identity == null ||
!user.Identity.IsAuthenticated;
return;
}
if (userIsAnonymous && !HasAllowAnonymous(context))
var authService = httpContext.RequestServices.GetRequiredService<IAuthorizationService>();
// Build a policy for the requested roles if specified
if (_rolesSplit != null)
{
var rolesPolicy = new AuthorizationPolicyBuilder();
rolesPolicy.RequiresRole(_rolesSplit);
if (!await authService.AuthorizeAsync(rolesPolicy.Build(), httpContext, context))
{
Fail(context);
return;
}
}
else
var authorized = (Policy == null)
// [Authorize] with no policy just requires any authenticated user
? await authService.AuthorizeAsync(BuildAnyAuthorizedUserPolicy(), httpContext, context)
: await authService.AuthorizeAsync(Policy, httpContext, context);
if (!authorized)
{
var authorizationService = httpContext.RequestServices.GetRequiredService<IAuthorizationService>();
if (authorizationService == null)
{
throw new InvalidOperationException(
Resources.AuthorizeAttribute_AuthorizationServiceMustBeDefined);
}
var authorized = await authorizationService.AuthorizeAsync(_claims, user);
if (!authorized)
{
Fail(context);
}
Fail(context);
}
}
private static AuthorizationPolicy BuildAnyAuthorizedUserPolicy()
{
return new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
}
public sealed override void OnAuthorization([NotNull] AuthorizationContext context)
{
// The async filter will be called by the filter pipeline.

View File

@ -0,0 +1,52 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
namespace Microsoft.AspNet.Mvc.Filters
{
/// <summary>
/// A filter implementation which delegates to the controller for action filter interfaces.
/// </summary>
public class ControllerActionFilter : IAsyncActionFilter, IOrderedFilter
{
// Controller-filter methods run farthest from the action by default.
/// <inheritdoc />
public int Order { get; set; } = int.MinValue;
/// <inheritdoc />
public async Task OnActionExecutionAsync(
[NotNull] ActionExecutingContext context,
[NotNull] ActionExecutionDelegate next)
{
var controller = context.Controller;
if (controller == null)
{
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
nameof(context.Controller),
nameof(ActionExecutingContext)));
}
IAsyncActionFilter asyncActionFilter;
IActionFilter actionFilter;
if ((asyncActionFilter = controller as IAsyncActionFilter) != null)
{
await asyncActionFilter.OnActionExecutionAsync(context, next);
}
else if ((actionFilter = controller as IActionFilter) != null)
{
actionFilter.OnActionExecuting(context);
if (context.Result == null)
{
actionFilter.OnActionExecuted(await next());
}
}
else
{
await next();
}
}
}
}

View File

@ -0,0 +1,52 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core;
namespace Microsoft.AspNet.Mvc.Filters
{
/// <summary>
/// A filter implementation which delegates to the controller for result filter interfaces.
/// </summary>
public class ControllerResultFilter : IAsyncResultFilter, IOrderedFilter
{
// Controller-filter methods run farthest from the result by default.
/// <inheritdoc />
public int Order { get; set; } = int.MinValue;
/// <inheritdoc />
public async Task OnResultExecutionAsync(
[NotNull] ResultExecutingContext context,
[NotNull] ResultExecutionDelegate next)
{
var controller = context.Controller;
if (controller == null)
{
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
nameof(context.Controller),
nameof(ResultExecutingContext)));
}
IAsyncResultFilter asyncResultFilter;
IResultFilter resultFilter;
if ((asyncResultFilter = controller as IAsyncResultFilter) != null)
{
await asyncResultFilter.OnResultExecutionAsync(context, next);
}
else if ((resultFilter = controller as IResultFilter) != null)
{
resultFilter.OnResultExecuting(context);
if (!context.Cancel)
{
resultFilter.OnResultExecuted(await next());
}
}
else
{
await next();
}
}
}
}

View File

@ -32,12 +32,6 @@ namespace Microsoft.AspNet.Mvc.Filters
}
}
var controllerFilter = context.ActionContext.Controller as IFilter;
if (controllerFilter != null)
{
InsertControllerAsFilter(context, controllerFilter);
}
if (callNext != null)
{
callNext();
@ -73,28 +67,6 @@ namespace Microsoft.AspNet.Mvc.Filters
}
}
private void InsertControllerAsFilter(FilterProviderContext context, IFilter controllerFilter)
{
var descriptor = new FilterDescriptor(controllerFilter, FilterScope.Controller);
var item = new FilterItem(descriptor, controllerFilter);
// BinarySearch will return the index of where the item _should_be_ in the list.
//
// If index > 0:
// Other items in the list have the same order and scope - the item was 'found'.
//
// If index < 0:
// No other items in the list have the same order and scope - the item was not 'found'
// Index will be the bitwise compliment of of the 'right' location.
var index = context.Results.BinarySearch(item, FilterItemOrderComparer.Comparer);
if (index < 0)
{
index = ~index;
}
context.Results.Insert(index, item);
}
private void ApplyFilterToContainer(object actualFilter, IFilter filterMetadata)
{
Debug.Assert(actualFilter != null, "actualFilter should not be null");

View File

@ -0,0 +1,29 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Threading.Tasks;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// A filter which surrounds execution of model binding, the action (and filters) and the action result
/// (and filters).
/// </summary>
public interface IAsyncResourceFilter : IFilter
{
/// <summary>
/// Executes the resource filter.
/// </summary>
/// <param name="context">The <see cref="ResourceExecutingContext"/>.</param>
/// <param name="next">
/// The <see cref="ResourceExecutionDelegate"/>. Invoked to execute the next
/// resource filter, or the remainder of the pipeline.
/// </param>
/// <returns>
/// A <see cref="Task"/> which will complete when the remainder of the pipeline completes.
/// </returns>
Task OnResourceExecutionAsync(
[NotNull] ResourceExecutingContext context,
[NotNull] ResourceExecutionDelegate next);
}
}

Some files were not shown because too many files have changed in this diff Show More