Merge branch 'release' of github.com:aspnet/Mvc into release
This commit is contained in:
commit
58b5445215
|
|
@ -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
219
Mvc.sln
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 %*
|
||||
|
|
|
|||
4
build.sh
4
build.sh
|
|
@ -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 "$@"
|
||||
|
|
|
|||
|
|
@ -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")]
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 »</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>
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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/> *@
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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.
|
|
@ -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>
|
||||
|
|
@ -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")]
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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])
|
||||
{
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
|
||||
},
|
||||
"frameworks": {
|
||||
"aspnet50": {},
|
||||
"net45": { },
|
||||
"aspnet50": { },
|
||||
"aspnetcore50": {
|
||||
"dependencies": {
|
||||
"System.Reflection.Extensions": "4.0.0-beta-*",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue