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

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

View File

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

219
Mvc.sln
View File

@ -1,7 +1,7 @@
 
Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14 # Visual Studio 14
VisualStudioVersion = 14.0.22213.0 VisualStudioVersion = 14.0.22512.0
MinimumVisualStudioVersion = 10.0.40219.1 MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}"
EndProject EndProject
@ -49,18 +49,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RoutingWebSite", "test\WebS
EndProject 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}" 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 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}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorWebSite", "test\WebSites\RazorWebSite\RazorWebSite.kproj", "{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}"
EndProject EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FormatterWebSite", "test\WebSites\FormatterWebSite\FormatterWebSite.kproj", "{62735776-46FF-4170-9392-02E128A69B89}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FormatterWebSite", "test\WebSites\FormatterWebSite\FormatterWebSite.kproj", "{62735776-46FF-4170-9392-02E128A69B89}"
EndProject 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}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ModelBindingWebSite", "test\WebSites\ModelBindingWebSite\ModelBindingWebSite.kproj", "{EE1AB716-F102-4CA3-AD2C-214A44B459A0}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{FAD65E9C-3CF3-4F68-9757-C7358604030B}" 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 EndProject
Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RequestServicesWebSite", "test\WebSites\RequestServicesWebSite\RequestServicesWebSite.kproj", "{F12E9CF0-4958-40C6-A6CD-759185157F84}" Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RequestServicesWebSite", "test\WebSites\RequestServicesWebSite\RequestServicesWebSite.kproj", "{F12E9CF0-4958-40C6-A6CD-759185157F84}"
EndProject 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 Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU 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.ActiveCfg = Release|Any CPU
{5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Mixed Platforms.Build.0 = 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 {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.ActiveCfg = Debug|Any CPU
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Any CPU.Build.0 = Debug|Any CPU {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Mixed Platforms.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.ActiveCfg = Release|Any CPU
{62735776-46FF-4170-9392-02E128A69B89}.Release|Mixed Platforms.Build.0 = 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 {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.ActiveCfg = Debug|Any CPU
{EE1AB716-F102-4CA3-AD2C-214A44B459A0}.Debug|Any CPU.Build.0 = 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 {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.ActiveCfg = Release|Any CPU
{F12E9CF0-4958-40C6-A6CD-759185157F84}.Release|Mixed Platforms.Build.0 = 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 {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 EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE
@ -615,12 +721,8 @@ Global
{680D75ED-601F-4D86-B01B-1072D0C31B8C} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {680D75ED-601F-4D86-B01B-1072D0C31B8C} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{42CDBF4A-E238-4C0F-A416-44588363EB4C} = {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} {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} {B07CAF59-11ED-40E3-A5DB-E1178F84FA78} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{62735776-46FF-4170-9392-02E128A69B89} = {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} {EE1AB716-F102-4CA3-AD2C-214A44B459A0} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{C6E5AFFA-890A-448F-8DE3-878B1D3C9FC7} = {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} {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} {860119ED-3DB1-424D-8D0A-30132A8A7D96} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1}
{59E1BE90-92C1-4D35-ADCC-B69F49077C81} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {59E1BE90-92C1-4D35-ADCC-B69F49077C81} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C}
{F12E9CF0-4958-40C6-A6CD-759185157F84} = {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 EndGlobalSection
EndGlobal EndGlobal

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,10 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Rendering;
@ -89,6 +92,26 @@ namespace MvcSample.Web
return View("MyView", user); 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> /// <summary>
/// Action that exercises input formatter /// Action that exercises input formatter
/// </summary> /// </summary>

View File

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

View File

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

View File

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

View File

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

View File

@ -2,11 +2,11 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.IO; using System.Security.Claims;
using Microsoft.AspNet.Builder; using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc;
using Microsoft.AspNet.Mvc.Razor; using Microsoft.AspNet.Mvc.Razor;
using Microsoft.AspNet.Routing; using Microsoft.AspNet.Security;
using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.ConfigurationModel;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
using MvcSample.Web.Filters; using MvcSample.Web.Filters;
@ -25,10 +25,10 @@ namespace MvcSample.Web
{ {
app.UseFileServer(); app.UseFileServer();
#if ASPNET50 #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() var configuration = new Configuration()
.AddJsonFile(Path.Combine("App_Data", "config.json")) .AddJsonFile("config.json")
.AddEnvironmentVariables(); .AddEnvironmentVariables();
string diSystem; string diSystem;
if (configuration.TryGet("DependencyInjection", out diSystem) && if (configuration.TryGet("DependencyInjection", out diSystem) &&
@ -38,6 +38,19 @@ namespace MvcSample.Web
app.UseServices(services => 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.AddMvc();
services.AddSingleton<PassThroughAttribute>(); services.AddSingleton<PassThroughAttribute>();
services.AddSingleton<UserNameService>(); services.AddSingleton<UserNameService>();
@ -50,6 +63,8 @@ namespace MvcSample.Web
services.Configure<MvcOptions>(options => services.Configure<MvcOptions>(options =>
{ {
options.Filters.Add(typeof(PassThroughAttribute), order: 17); options.Filters.Add(typeof(PassThroughAttribute), order: 17);
options.AddXmlDataContractSerializerFormatter();
}); });
services.Configure<RazorViewEngineOptions>(options => services.Configure<RazorViewEngineOptions>(options =>
{ {
@ -90,6 +105,7 @@ namespace MvcSample.Web
services.Configure<MvcOptions>(options => services.Configure<MvcOptions>(options =>
{ {
options.Filters.Add(typeof(PassThroughAttribute), order: 17); options.Filters.Add(typeof(PassThroughAttribute), order: 17);
options.AddXmlDataContractSerializerFormatter();
}); });
}); });
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -15,4 +15,9 @@
<DevelopmentServerPort>30540</DevelopmentServerPort> <DevelopmentServerPort>30540</DevelopmentServerPort>
</PropertyGroup> </PropertyGroup>
<Import Project="$(VSToolsPath)\AspNet\Microsoft.Web.AspNet.targets" Condition="'$(VSToolsPath)' != ''" /> <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> </Project>

View File

@ -13,14 +13,15 @@
<form asp-anti-forgery="false" asp-action="Create"> <form asp-anti-forgery="false" asp-action="Create">
<div class="form-horizontal"> <div class="form-horizontal">
@* validation summary tag helper will target just <div/> elements and append the list of errors *@ @* 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 *@ @* - 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="ModelOnly") *@ @* 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 *@ @* 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 *@ @* initially at least, will not remove the <div/> if list isn't generated *@
@* - should helper 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) *@ @* - (Html.ValidationSummary returns empty string despite non-empty message parameter) *@
@* Acceptable values are: "None", "ModelOnly" and "All" *@ @* 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> <span style="color:red">This is my message</span>
</div> </div>
@ -47,8 +48,9 @@
<div class="form-group"> <div class="form-group">
<label asp-for="YearsEmployeed" class="control-label col-md-2" /> <label asp-for="YearsEmployeed" class="control-label col-md-2" />
<div class="col-md-10"> <div class="col-md-10">
@* <select/> tag helper has ContentBehavior.Append -- items render after static options *@ @* <select/> items render after static options *@
<select asp-for="YearsEmployeed" asp-items="(IEnumerable<SelectListItem>)ViewBag.Items" size="2" class="form-control"> @{ 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/> *@ @* 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" *@ @* - @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/> *@ @* - wouldn't help if user selected one static <option/> and expression indicated another, especially one earlier in the <select/> *@

View File

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,160 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc
{
/// <summary>
/// Specifies the allowed content types which can be used to select the action based on request's content-type.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ConsumesAttribute : Attribute, IResourceFilter, IConsumesActionConstraint
{
public static readonly int ConsumesActionConstraintOrder = 200;
/// <summary>
/// Creates a new instance of <see cref="ConsumesAttribute"/>.
/// </summary>
public ConsumesAttribute([NotNull] string contentType, params string[] otherContentTypes)
{
ContentTypes = GetContentTypes(contentType, otherContentTypes);
}
// The value used is a non default value so that it avoids getting mixed with other action constraints
// with default order.
/// <inheritdoc />
int IActionConstraint.Order { get; } = ConsumesActionConstraintOrder;
/// <inheritdoc />
public IList<MediaTypeHeaderValue> ContentTypes { get; set; }
/// <inheritdoc />
public void OnResourceExecuting([NotNull] ResourceExecutingContext context)
{
// Only execute if the current filter is the one which is closest to the action.
// Ignore all other filters. This is to ensure we have a overriding behavior.
if (IsApplicable(context.ActionDescriptor))
{
MediaTypeHeaderValue requestContentType = null;
MediaTypeHeaderValue.TryParse(context.HttpContext.Request.ContentType, out requestContentType);
// Only execute if this is the last filter before calling the action.
// This ensures that we only run the filter which is closest to the action.
if (requestContentType != null &&
!ContentTypes.Any(contentType => contentType.IsSubsetOf(requestContentType)))
{
context.Result = new UnsupportedMediaTypeResult();
}
}
}
/// <inheritdoc />
public void OnResourceExecuted([NotNull] ResourceExecutedContext context)
{
}
public bool Accept(ActionConstraintContext context)
{
// If this constraint is not closest to the action, it will be skipped.
if (!IsApplicable(context.CurrentCandidate.Action))
{
// Since the constraint is to be skipped, returning true here
// will let the current candidate ignore this constraint and will
// be selected based on other constraints for this action.
return true;
}
MediaTypeHeaderValue requestContentType = null;
MediaTypeHeaderValue.TryParse(context.RouteContext.HttpContext.Request.ContentType, out requestContentType);
// If the request content type is null we need to act like pass through.
// In case there is a single candidate with a constraint it should be selected.
// If there are multiple actions with consumes action constraints this should result in ambiguous exception
// unless there is another action without a consumes constraint.
if (requestContentType == null)
{
var isActionWithoutConsumeConstraintPresent = context.Candidates.Any(
candidate => candidate.Constraints == null ||
!candidate.Constraints.Any(constraint => constraint is IConsumesActionConstraint));
return !isActionWithoutConsumeConstraintPresent;
}
if (ContentTypes.Any(c => c.IsSubsetOf(requestContentType)))
{
return true;
}
var firstCandidate = context.Candidates[0];
if (firstCandidate != context.CurrentCandidate)
{
// If the current candidate is not same as the first candidate,
// we need not probe other candidates to see if they apply.
// Only the first candidate is allowed to probe other candidates and based on the result select itself.
return false;
}
// Run the matching logic for all IConsumesActionConstraints we can find, and see what matches.
// 1). If we have a unique best match, then only that constraint should return true.
// 2). If we have multiple matches, then all constraints that match will return true
// , resulting in ambiguity(maybe).
// 3). If we have no matches, then we choose the first constraint to return true.It will later return a 415
foreach (var candidate in context.Candidates)
{
if (candidate == firstCandidate)
{
continue;
}
var tempContext = new ActionConstraintContext()
{
Candidates = context.Candidates,
RouteContext = context.RouteContext,
CurrentCandidate = candidate
};
if (candidate.Constraints == null || candidate.Constraints.Count() == 0 ||
candidate.Constraints.Any(constraint => constraint is IConsumesActionConstraint &&
constraint.Accept(tempContext)))
{
// There is someone later in the chain which can handle the request.
// end the process here.
return false;
}
}
// There is no one later in the chain that can handle this content type return a false positive so that
// later we can detect and return a 415.
return true;
}
private bool IsApplicable(ActionDescriptor actionDescriptor)
{
// If there are multiple IConsumeActionConstraints which are defined at the class and
// at the action level, the one closest to the action overrides the others. To ensure this
// we take advantage of the fact that ConsumesAttribute is both an IActionFilter and an
// IConsumeActionConstraint. Since filterdescriptor collection is ordered (the last filter is the one
// closest to the action), we apply this constraint only if there is no IConsumeActionConstraint after this.
return actionDescriptor.FilterDescriptors.Last(
filter => filter.Filter is IConsumesActionConstraint).Filter == this;
}
private List<MediaTypeHeaderValue> GetContentTypes(string firstArg, string[] args)
{
var contentTypes = new List<MediaTypeHeaderValue>();
contentTypes.Add(MediaTypeHeaderValue.Parse(firstArg));
foreach (var item in args)
{
var contentType = MediaTypeHeaderValue.Parse(item);
contentTypes.Add(contentType);
}
return contentTypes;
}
}
}

View File

@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc
/// are candidates for selection, the next stage to run is the lowest value of <see cref="Order"/> for any /// 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. /// 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 /// 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 /// 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 /// process will repeat with the next stage on the set of 'best' actions. If after processing the

View File

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

View File

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

View File

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

View File

@ -1,21 +1,19 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Net; namespace Microsoft.AspNet.Mvc
using Microsoft.AspNet.Mvc;
namespace System.Web.Http
{ {
/// <summary> /// <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> /// </summary>
public class BadRequestResult : HttpStatusCodeResult public class BadRequestResult : HttpStatusCodeResult
{ {
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BadRequestResult"/> class. /// Creates a new <see cref="BadRequestResult"/> instance.
/// </summary> /// </summary>
public BadRequestResult() public BadRequestResult()
: base((int)HttpStatusCode.BadRequest) : base(400)
{ {
} }
} }

View File

@ -4,6 +4,7 @@
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
@ -15,18 +16,36 @@ namespace Microsoft.AspNet.Mvc
public string ContentType { get; set; } 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) public override async Task ExecuteResultAsync([NotNull] ActionContext context)
{ {
var response = context.HttpContext.Response; 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) if (Content != null)
{ {
await response.WriteAsync(Content); await response.WriteAsync(Content, contentTypeHeader.Encoding);
} }
} }
} }

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // 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;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Http; using Microsoft.AspNet.Http;
@ -13,6 +14,19 @@ namespace Microsoft.AspNet.Mvc
/// </summary> /// </summary>
public class FileContentResult : FileResult 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> /// <summary>
/// Creates a new <see cref="FileContentResult"/> instance with /// Creates a new <see cref="FileContentResult"/> instance with
/// the provided <paramref name="fileContents"/> and the /// the provided <paramref name="fileContents"/> and the
@ -27,9 +41,24 @@ namespace Microsoft.AspNet.Mvc
} }
/// <summary> /// <summary>
/// Gets the file contents. /// Gets or sets the file contents.
/// </summary> /// </summary>
public byte[] FileContents { get; private set; } public byte[] FileContents
{
get
{
return _fileContents;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_fileContents = value;
}
}
/// <inheritdoc /> /// <inheritdoc />
protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation) protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)

View File

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

View File

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

View File

@ -1,6 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // 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.IO;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -17,6 +18,19 @@ namespace Microsoft.AspNet.Mvc
// default buffer size as defined in BufferedStream type // default buffer size as defined in BufferedStream type
private const int BufferSize = 0x1000; 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> /// <summary>
/// Creates a new <see cref="FileStreamResult"/> instance with /// Creates a new <see cref="FileStreamResult"/> instance with
/// the provided <paramref name="fileStream"/> and the /// the provided <paramref name="fileStream"/> and the
@ -31,9 +45,24 @@ namespace Microsoft.AspNet.Mvc
} }
/// <summary> /// <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> /// </summary>
public Stream FileStream { get; private set; } public Stream FileStream
{
get
{
return _fileStream;
}
set
{
if (value == null)
{
throw new ArgumentNullException("value");
}
_fileStream = value;
}
}
/// <inheritdoc /> /// <inheritdoc />
protected async override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation) protected async override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation)

View File

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

View File

@ -1,23 +1,13 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // 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 namespace Microsoft.AspNet.Mvc
{ {
public class NoContentResult : ActionResult public class NoContentResult : HttpStatusCodeResult
{ {
public override void ExecuteResult([NotNull] ActionContext context) public NoContentResult()
: base(204)
{ {
var response = context.HttpContext.Response;
#if ASPNET50
response.StatusCode = (int)HttpStatusCode.NoContent;
#else
response.StatusCode = 204;
#endif
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,12 @@ namespace Microsoft.AspNet.Mvc
public class ViewResult : ActionResult public class ViewResult : ActionResult
{ {
/// <summary> /// <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> /// </summary>
/// <remarks> /// <remarks>
/// When <c>null</c>, defaults to <see cref="ActionDescriptor.Name"/>. /// When <c>null</c>, defaults to <see cref="ActionDescriptor.Name"/>.
@ -44,6 +49,11 @@ namespace Microsoft.AspNet.Mvc
.EnsureSuccessful() .EnsureSuccessful()
.View; .View;
if (StatusCode != null)
{
context.HttpContext.Response.StatusCode = StatusCode.Value;
}
using (view as IDisposable) using (view as IDisposable)
{ {
await ViewExecutor.ExecuteAsync(view, context, ViewData, contentType: null); await ViewExecutor.ExecuteAsync(view, context, ViewData, contentType: null);

View File

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

View File

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

View File

@ -23,7 +23,8 @@ namespace Microsoft.AspNet.Mvc
public AntiForgeryToken GetCookieToken(HttpContext httpContext) public AntiForgeryToken GetCookieToken(HttpContext httpContext)
{ {
var contextAccessor = httpContext.RequestServices.GetRequiredService<IContextAccessor<AntiForgeryContext>>(); var contextAccessor =
httpContext.RequestServices.GetRequiredService<IScopedInstance<AntiForgeryContext>>();
if (contextAccessor.Value != null) if (contextAccessor.Value != null)
{ {
return contextAccessor.Value.CookieToken; return contextAccessor.Value.CookieToken;
@ -41,7 +42,7 @@ namespace Microsoft.AspNet.Mvc
public async Task<AntiForgeryToken> GetFormTokenAsync(HttpContext httpContext) public async Task<AntiForgeryToken> GetFormTokenAsync(HttpContext httpContext)
{ {
var form = await httpContext.Request.GetFormAsync(); var form = await httpContext.Request.ReadFormAsync();
var value = form[_config.FormFieldName]; var value = form[_config.FormFieldName];
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
{ {
@ -56,9 +57,10 @@ namespace Microsoft.AspNet.Mvc
{ {
// Add the cookie to the request based context. // 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. // 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."); 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 serializedToken = _serializer.Serialize(token);
var options = new CookieOptions() { HttpOnly = true }; var options = new CookieOptions() { HttpOnly = true };

View File

@ -102,19 +102,8 @@ namespace Microsoft.AspNet.Mvc
var tokenSet = GetTokens(httpContext, oldCookieToken); var tokenSet = GetTokens(httpContext, oldCookieToken);
var newCookieToken = tokenSet.CookieToken; var newCookieToken = tokenSet.CookieToken;
var formToken = tokenSet.FormToken; var formToken = tokenSet.FormToken;
if (newCookieToken != null)
{
// If a new cookie was generated, persist it.
_tokenStore.SaveCookieToken(httpContext, newCookieToken);
}
if (!_config.SuppressXFrameOptionsHeader) SaveCookieTokenAndHeader(httpContext, newCookieToken);
{
// 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");
}
// <input type="hidden" name="__AntiForgeryToken" value="..." /> // <input type="hidden" name="__AntiForgeryToken" value="..." />
var retVal = new TagBuilder("input"); var retVal = new TagBuilder("input");
@ -143,15 +132,11 @@ namespace Microsoft.AspNet.Mvc
private AntiForgeryTokenSetInternal GetTokens(HttpContext httpContext, AntiForgeryToken oldCookieToken) private AntiForgeryTokenSetInternal GetTokens(HttpContext httpContext, AntiForgeryToken oldCookieToken)
{ {
AntiForgeryToken newCookieToken = null; var newCookieToken = ValidateAndGenerateNewToken(oldCookieToken);
if (!_validator.IsCookieTokenValid(oldCookieToken)) if (newCookieToken != null)
{ {
// Need to make sure we're always operating with a good cookie token. oldCookieToken = newCookieToken;
oldCookieToken = newCookieToken = _generator.GenerateCookieToken();
} }
Debug.Assert(_validator.IsCookieTokenValid(oldCookieToken));
var formToken = _generator.GenerateFormToken( var formToken = _generator.GenerateFormToken(
httpContext, httpContext,
ExtractIdentity(httpContext), ExtractIdentity(httpContext),
@ -204,6 +189,54 @@ namespace Microsoft.AspNet.Mvc
deserializedFormToken); 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 private class AntiForgeryTokenSetInternal
{ {
public AntiForgeryToken FormToken { get; set; } public AntiForgeryToken FormToken { get; set; }

View File

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

View File

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

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Text; using System.Text;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Mvc.Routing;
@ -123,28 +124,28 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
return right; return right;
} }
if (left.EndsWith("/", StringComparison.OrdinalIgnoreCase)) if (left.EndsWith("/", StringComparison.Ordinal))
{ {
return left + right; return left + right;
} }
// Both templates contain some text. // Both templates contain some text.
return left + '/' + right; return left + "/" + right;
} }
private static bool IsOverridePattern(string template) private static bool IsOverridePattern(string template)
{ {
return template != null && return template != null &&
(template.StartsWith("~/", StringComparison.OrdinalIgnoreCase) || (template.StartsWith("~/", StringComparison.Ordinal) ||
template.StartsWith("/", StringComparison.OrdinalIgnoreCase)); template.StartsWith("/", StringComparison.Ordinal));
} }
private static bool IsEmptyLeftSegment(string template) private static bool IsEmptyLeftSegment(string template)
{ {
return template == null || return template == null ||
template.Equals(string.Empty, StringComparison.OrdinalIgnoreCase) || template.Equals(string.Empty, StringComparison.Ordinal) ||
template.Equals("~/", StringComparison.OrdinalIgnoreCase) || template.Equals("~/", StringComparison.Ordinal) ||
template.Equals("/", StringComparison.OrdinalIgnoreCase); template.Equals("/", StringComparison.Ordinal);
} }
private static string CleanTemplate(string result) 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 // This is an invalid combined template, so we don't want to
// accidentally clean it and produce a valid template. For that // accidentally clean it and produce a valid template. For that
// reason we ignore the clean up process for it. // reason we ignore the clean up process for it.
if (result.Equals("//", StringComparison.OrdinalIgnoreCase)) if (result.Equals("//", StringComparison.Ordinal))
{ {
return result; return result;
} }
var startIndex = 0; var startIndex = 0;
if (result.StartsWith("/", StringComparison.OrdinalIgnoreCase)) if (result.StartsWith("/", StringComparison.Ordinal))
{ {
startIndex = 1; startIndex = 1;
} }
else if (result.StartsWith("~/", StringComparison.OrdinalIgnoreCase)) else if (result.StartsWith("~/", StringComparison.Ordinal))
{ {
startIndex = 2; startIndex = 2;
} }
@ -179,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
} }
var subStringLength = result.Length - startIndex; var subStringLength = result.Length - startIndex;
if (result.EndsWith("/", StringComparison.OrdinalIgnoreCase)) if (result.EndsWith("/", StringComparison.Ordinal))
{ {
subStringLength--; subStringLength--;
} }
@ -336,7 +337,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels
var message = Resources.FormatAttributeRoute_TokenReplacement_ReplacementValueNotFound( var message = Resources.FormatAttributeRoute_TokenReplacement_ReplacementValueNotFound(
template, template,
token, token,
string.Join(", ", values.Keys)); string.Join(", ", values.Keys.OrderBy(k => k, StringComparer.OrdinalIgnoreCase)));
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -16,10 +16,10 @@ using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
{ {
public class Controller : IActionFilter, IAsyncActionFilter, IOrderedFilter, IDisposable public class Controller : IActionFilter, IAsyncActionFilter, IDisposable
{ {
private DynamicViewData _viewBag; private DynamicViewData _viewBag;
private IViewEngine _viewEngine; private ViewDataDictionary _viewData;
public IServiceProvider Resolver public IServiceProvider Resolver
{ {
@ -59,28 +59,8 @@ namespace Microsoft.AspNet.Mvc
{ {
return ActionContext?.RouteData; return ActionContext?.RouteData;
} }
}
public IViewEngine ViewEngine
{
get
{
if (_viewEngine == null)
{
_viewEngine = ActionContext?.
HttpContext?.
RequestServices.GetRequiredService<ICompositeViewEngine>();
}
return _viewEngine;
} }
set
{
_viewEngine = value;
}
}
public ModelStateDictionary ModelState public ModelStateDictionary ModelState
{ {
get get
@ -93,10 +73,13 @@ namespace Microsoft.AspNet.Mvc
public ActionContext ActionContext { get; set; } public ActionContext ActionContext { get; set; }
[Activate] [Activate]
public IUrlHelper Url { get; set; } public ActionBindingContext BindingContext { get; set; }
[Activate] [Activate]
public IActionBindingContextProvider BindingContextProvider { get; set; } public IModelMetadataProvider MetadataProvider { get; set; }
[Activate]
public IUrlHelper Url { get; set; }
public IPrincipal User 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] [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 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> /// <summary>
/// Creates a <see cref="ViewResult"/> object that renders a view to the response. /// Creates a <see cref="ViewResult"/> object that renders a view to the response.
/// </summary> /// </summary>
@ -184,7 +190,6 @@ namespace Microsoft.AspNet.Mvc
{ {
ViewName = viewName, ViewName = viewName,
ViewData = ViewData, ViewData = ViewData,
ViewEngine = _viewEngine,
}; };
} }
@ -282,18 +287,10 @@ namespace Microsoft.AspNet.Mvc
var result = new ContentResult var result = new ContentResult
{ {
Content = content, Content = content,
ContentEncoding = contentEncoding,
ContentType = contentType
}; };
if (contentType != null)
{
result.ContentType = contentType;
}
if (contentEncoding != null)
{
result.ContentEncoding = contentEncoding;
}
return result; return result;
} }
@ -392,8 +389,10 @@ namespace Microsoft.AspNet.Mvc
public virtual RedirectToActionResult RedirectToAction(string actionName, string controllerName, public virtual RedirectToActionResult RedirectToAction(string actionName, string controllerName,
object routeValues) object routeValues)
{ {
return new RedirectToActionResult(Url, actionName, controllerName, return new RedirectToActionResult(actionName, controllerName, TypeHelper.ObjectToDictionary(routeValues))
TypeHelper.ObjectToDictionary(routeValues)); {
UrlHelper = Url,
};
} }
/// <summary> /// <summary>
@ -447,8 +446,14 @@ namespace Microsoft.AspNet.Mvc
public virtual RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName, public virtual RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName,
object routeValues) object routeValues)
{ {
return new RedirectToActionResult(Url, actionName, controllerName, return new RedirectToActionResult(
TypeHelper.ObjectToDictionary(routeValues), permanent: true); actionName,
controllerName,
TypeHelper.ObjectToDictionary(routeValues),
permanent: true)
{
UrlHelper = Url,
};
} }
/// <summary> /// <summary>
@ -483,7 +488,10 @@ namespace Microsoft.AspNet.Mvc
[NonAction] [NonAction]
public virtual RedirectToRouteResult RedirectToRoute(string routeName, object routeValues) public virtual RedirectToRouteResult RedirectToRoute(string routeName, object routeValues)
{ {
return new RedirectToRouteResult(Url, routeName, routeValues); return new RedirectToRouteResult(routeName, routeValues)
{
UrlHelper = Url,
};
} }
/// <summary> /// <summary>
@ -520,7 +528,10 @@ namespace Microsoft.AspNet.Mvc
[NonAction] [NonAction]
public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, object routeValues) 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> /// <summary>
@ -617,6 +628,148 @@ namespace Microsoft.AspNet.Mvc
return new HttpNotFoundResult(); 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> /// <summary>
/// Called before the action method is invoked. /// Called before the action method is invoked.
/// </summary> /// </summary>
@ -655,7 +808,7 @@ namespace Microsoft.AspNet.Mvc
} }
/// <summary> /// <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"/>. /// <see cref="IValueProvider"/>.
/// </summary> /// </summary>
/// <typeparam name="TModel">The type of the model object.</typeparam> /// <typeparam name="TModel">The type of the model object.</typeparam>
@ -682,15 +835,15 @@ namespace Microsoft.AspNet.Mvc
[NotNull] string prefix) [NotNull] string prefix)
where TModel : class where TModel : class
{ {
if (BindingContextProvider == null) if (BindingContext == null)
{ {
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), var message = Resources.FormatPropertyOfTypeCannotBeNull(
GetType().FullName); nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message); 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> /// <summary>
@ -709,22 +862,23 @@ namespace Microsoft.AspNet.Mvc
[NotNull] IValueProvider valueProvider) [NotNull] IValueProvider valueProvider)
where TModel : class where TModel : class
{ {
if (BindingContextProvider == null) if (BindingContext == null)
{ {
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), var message = Resources.FormatPropertyOfTypeCannotBeNull(
GetType().FullName); nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext); return await ModelBindingHelper.TryUpdateModelAsync(
return await ModelBindingHelper.TryUpdateModelAsync(model, model,
prefix, prefix,
ActionContext.HttpContext, ActionContext.HttpContext,
ModelState, ModelState,
bindingContext.MetadataProvider, MetadataProvider,
bindingContext.ModelBinder, BindingContext.ModelBinder,
valueProvider, valueProvider,
bindingContext.ValidatorProvider); BindingContext.ValidatorProvider);
} }
/// <summary> /// <summary>
@ -745,23 +899,24 @@ namespace Microsoft.AspNet.Mvc
[NotNull] params Expression<Func<TModel, object>>[] includeExpressions) [NotNull] params Expression<Func<TModel, object>>[] includeExpressions)
where TModel : class where TModel : class
{ {
if (BindingContextProvider == null) if (BindingContext == null)
{ {
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), var message = Resources.FormatPropertyOfTypeCannotBeNull(
GetType().FullName); nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext); return await ModelBindingHelper.TryUpdateModelAsync(
return await ModelBindingHelper.TryUpdateModelAsync(model, model,
prefix, prefix,
ActionContext.HttpContext, ActionContext.HttpContext,
ModelState, ModelState,
bindingContext.MetadataProvider, MetadataProvider,
bindingContext.ModelBinder, BindingContext.ModelBinder,
bindingContext.ValueProvider, BindingContext.ValueProvider,
bindingContext.ValidatorProvider, BindingContext.ValidatorProvider,
includeExpressions); includeExpressions);
} }
/// <summary> /// <summary>
@ -781,23 +936,24 @@ namespace Microsoft.AspNet.Mvc
[NotNull] Func<ModelBindingContext, string, bool> predicate) [NotNull] Func<ModelBindingContext, string, bool> predicate)
where TModel : class where TModel : class
{ {
if (BindingContextProvider == null) if (BindingContext == null)
{ {
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), var message = Resources.FormatPropertyOfTypeCannotBeNull(
GetType().FullName); nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext); return await ModelBindingHelper.TryUpdateModelAsync(
return await ModelBindingHelper.TryUpdateModelAsync(model, model,
prefix, prefix,
ActionContext.HttpContext, ActionContext.HttpContext,
ModelState, ModelState,
bindingContext.MetadataProvider, MetadataProvider,
bindingContext.ModelBinder, BindingContext.ModelBinder,
bindingContext.ValueProvider, BindingContext.ValueProvider,
bindingContext.ValidatorProvider, BindingContext.ValidatorProvider,
predicate); predicate);
} }
/// <summary> /// <summary>
@ -820,23 +976,24 @@ namespace Microsoft.AspNet.Mvc
[NotNull] params Expression<Func<TModel, object>>[] includeExpressions) [NotNull] params Expression<Func<TModel, object>>[] includeExpressions)
where TModel : class where TModel : class
{ {
if (BindingContextProvider == null) if (BindingContext == null)
{ {
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), var message = Resources.FormatPropertyOfTypeCannotBeNull(
GetType().FullName); nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext); return await ModelBindingHelper.TryUpdateModelAsync(
return await ModelBindingHelper.TryUpdateModelAsync(model, model,
prefix, prefix,
ActionContext.HttpContext, ActionContext.HttpContext,
ModelState, ModelState,
bindingContext.MetadataProvider, MetadataProvider,
bindingContext.ModelBinder, BindingContext.ModelBinder,
valueProvider, valueProvider,
bindingContext.ValidatorProvider, BindingContext.ValidatorProvider,
includeExpressions); includeExpressions);
} }
/// <summary> /// <summary>
@ -858,23 +1015,75 @@ namespace Microsoft.AspNet.Mvc
[NotNull] Func<ModelBindingContext, string, bool> predicate) [NotNull] Func<ModelBindingContext, string, bool> predicate)
where TModel : class where TModel : class
{ {
if (BindingContextProvider == null) if (BindingContext == null)
{ {
var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), var message = Resources.FormatPropertyOfTypeCannotBeNull(
GetType().FullName); nameof(BindingContext),
typeof(Controller).FullName);
throw new InvalidOperationException(message); throw new InvalidOperationException(message);
} }
var bindingContext = await BindingContextProvider.GetActionBindingContextAsync(ActionContext); return await ModelBindingHelper.TryUpdateModelAsync(
return await ModelBindingHelper.TryUpdateModelAsync(model, model,
prefix, prefix,
ActionContext.HttpContext, ActionContext.HttpContext,
ModelState, ModelState,
bindingContext.MetadataProvider, MetadataProvider,
bindingContext.ModelBinder, BindingContext.ModelBinder,
valueProvider, valueProvider,
bindingContext.ValidatorProvider, BindingContext.ValidatorProvider,
predicate); 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() public void Dispose()

View File

@ -56,11 +56,7 @@ namespace Microsoft.AspNet.Mvc
actionDescriptor.ControllerTypeInfo = controller.ControllerType; actionDescriptor.ControllerTypeInfo = controller.ControllerType;
AddApiExplorerInfo(actionDescriptor, action, controller); AddApiExplorerInfo(actionDescriptor, action, controller);
AddRouteConstraints(actionDescriptor, controller, action); AddRouteConstraints(removalConstraints, actionDescriptor, controller, action);
AddControllerRouteConstraints(
actionDescriptor,
controller.RouteConstraints,
removalConstraints);
if (IsAttributeRoutedAction(actionDescriptor)) if (IsAttributeRoutedAction(actionDescriptor))
{ {
@ -279,7 +275,6 @@ namespace Microsoft.AspNet.Mvc
var parameterDescriptor = new ParameterDescriptor() var parameterDescriptor = new ParameterDescriptor()
{ {
BinderMetadata = parameter.BinderMetadata, BinderMetadata = parameter.BinderMetadata,
IsOptional = parameter.IsOptional,
Name = parameter.ParameterName, Name = parameter.ParameterName,
ParameterType = parameter.ParameterInfo.ParameterType, ParameterType = parameter.ParameterInfo.ParameterType,
}; };
@ -295,6 +290,13 @@ namespace Microsoft.AspNet.Mvc
var apiExplorerIsVisible = action.ApiExplorer?.IsVisible ?? controller.ApiExplorer?.IsVisible ?? false; var apiExplorerIsVisible = action.ApiExplorer?.IsVisible ?? controller.ApiExplorer?.IsVisible ?? false;
if (apiExplorerIsVisible) 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() var apiExplorerActionData = new ApiDescriptionActionData()
{ {
GroupName = action.ApiExplorer?.GroupName ?? controller.ApiExplorer?.GroupName, GroupName = action.ApiExplorer?.GroupName ?? controller.ApiExplorer?.GroupName,
@ -371,39 +373,17 @@ namespace Microsoft.AspNet.Mvc
} }
public static void AddRouteConstraints( public static void AddRouteConstraints(
ISet<string> removalConstraints,
ControllerActionDescriptor actionDescriptor, ControllerActionDescriptor actionDescriptor,
ControllerModel controller, ControllerModel controller,
ActionModel action) ActionModel action)
{ {
actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( // Apply all the constraints defined on the action, then controller (for example, [Area])
"controller", // to the actions. Also keep track of all the constraints that require preventing actions
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
// without the constraint to match. For example, actions without an [Area] attribute on their // 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 // controller should not match when a value has been given for area when matching a url or
// generating a link. // generating a link.
foreach (var constraintAttribute in routeconstraints) foreach (var constraintAttribute in action.RouteConstraints)
{ {
if (constraintAttribute.BlockNonAttributedActions) 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) private static bool HasConstraint(List<RouteDataActionConstraint> constraints, string routeKey)

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc
public class DefaultAssemblyProvider : IAssemblyProvider public class DefaultAssemblyProvider : IAssemblyProvider
{ {
/// <summary> /// <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. /// MVC controllers, view components and views.
/// </summary> /// </summary>
protected virtual HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.Ordinal) protected virtual HashSet<string> ReferenceAssemblies { get; } = new HashSet<string>(StringComparer.Ordinal)
@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc
/// <summary> /// <summary>
/// Returns a list of libraries that references the assemblies in <see cref="ReferenceAssemblies"/>. /// 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. /// while ignoring MVC assemblies.
/// </summary> /// </summary>
/// <returns>A set of <see cref="ILibraryInformation"/>.</returns> /// <returns>A set of <see cref="ILibraryInformation"/>.</returns>

View File

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

View File

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

View File

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

View File

@ -17,7 +17,8 @@ namespace Microsoft.AspNet.Mvc
public class DefaultPropertyBindingPredicateProvider<TModel> : IPropertyBindingPredicateProvider public class DefaultPropertyBindingPredicateProvider<TModel> : IPropertyBindingPredicateProvider
where TModel : class 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> /// <summary>
/// The prefix which is used while generating the property filter. /// 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()); var expression = ModelBindingHelper.GetIncludePredicateExpression(Prefix, includeExpressions.ToArray());
return expression.Compile(); return expression.Compile();

View File

@ -2,28 +2,38 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic;
using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.AspNet.Routing;
namespace Microsoft.AspNet.Mvc.Description namespace Microsoft.AspNet.Mvc.Description
{ {
/// <summary>
/// A metadata description of an input to an API.
/// </summary>
public class ApiParameterDescription public class ApiParameterDescription
{ {
public bool IsOptional { get; set; } /// <summary>
/// Gets or sets the <see cref="ModelMetadata"/>.
/// </summary>
public ModelMetadata ModelMetadata { get; set; } public ModelMetadata ModelMetadata { get; set; }
/// <summary>
/// Gets or sets the name.
/// </summary>
public string Name { get; set; } 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 ApiParameterSource Source { get; set; }
public IEnumerable<IRouteConstraint> Constraints { get; set; } /// <summary>
/// Gets or sets the parameter type.
public object DefaultValue { get; set; } /// </summary>
public Type Type { get; set; } public Type Type { get; set; }
} }
} }

View File

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

View File

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

View File

@ -1,7 +1,7 @@
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // 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. // 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 namespace Microsoft.AspNet.Mvc.Description
{ {

View File

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

View File

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

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Runtime.ExceptionServices; using System.Runtime.ExceptionServices;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Core;
using Microsoft.AspNet.Mvc.ModelBinding;
using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection;
namespace Microsoft.AspNet.Mvc namespace Microsoft.AspNet.Mvc
@ -15,14 +16,22 @@ namespace Microsoft.AspNet.Mvc
public abstract class FilterActionInvoker : IActionInvoker public abstract class FilterActionInvoker : IActionInvoker
{ {
private readonly INestedProviderManager<FilterProviderContext> _filterProvider; 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 IFilter[] _filters;
private FilterCursor _cursor; private FilterCursor _cursor;
private ExceptionContext _exceptionContext;
private AuthorizationContext _authorizationContext; private AuthorizationContext _authorizationContext;
private ResourceExecutingContext _resourceExecutingContext;
private ResourceExecutedContext _resourceExecutedContext;
private ExceptionContext _exceptionContext;
private ActionExecutingContext _actionExecutingContext; private ActionExecutingContext _actionExecutingContext;
private ActionExecutedContext _actionExecutedContext; private ActionExecutedContext _actionExecutedContext;
@ -31,17 +40,57 @@ namespace Microsoft.AspNet.Mvc
public FilterActionInvoker( public FilterActionInvoker(
[NotNull] ActionContext actionContext, [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; ActionContext = actionContext;
_filterProvider = filterProvider; _filterProvider = filterProvider;
_inputFormatterProvider = inputFormatterProvider;
_modelBinderProvider = modelBinderProvider;
_modelValidatorProviderProvider = modelValidatorProviderProvider;
_valueProviderFactoryProvider = valueProviderFactoryProvider;
_actionBindingContextAccessor = actionBindingContextAccessor;
} }
protected ActionContext ActionContext { get; private set; } 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<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() public virtual async Task InvokeAsync()
{ {
@ -59,38 +108,35 @@ namespace Microsoft.AspNet.Mvc
return; return;
} }
// >> ExceptionFilters >> ActionFilters >> Action try
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)
{ {
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 // Release the instance after all filters have run. We don't need to surround
if (_exceptionContext.ExceptionDispatchInfo != null) // 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 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() 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() private async Task InvokeAllExceptionFiltersAsync()
{ {
_cursor.SetStage(FilterStage.ExceptionFilters); _cursor.SetStage(FilterStage.ExceptionFilters);
@ -219,8 +407,33 @@ namespace Microsoft.AspNet.Mvc
private async Task InvokeAllActionFiltersAsync() private async Task InvokeAllActionFiltersAsync()
{ {
_cursor.SetStage(FilterStage.ActionFilters); _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(); 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. // If we get here, it means that an async filter set a result AND called next(). This is forbidden.
var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit( var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit(
typeof(IAsyncActionFilter).Name, typeof(IAsyncActionFilter).Name,
"Result", nameof(ActionExecutingContext.Result),
typeof(ActionExecutingContext).Name, typeof(ActionExecutingContext).Name,
typeof(ActionExecutionDelegate).Name); typeof(ActionExecutionDelegate).Name);
@ -249,7 +462,10 @@ namespace Microsoft.AspNet.Mvc
if (_actionExecutedContext == null) if (_actionExecutedContext == null)
{ {
// If we get here then the filter didn't call 'next' indicating a short circuit // 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, Canceled = true,
Result = _actionExecutingContext.Result, Result = _actionExecutingContext.Result,
@ -263,7 +479,10 @@ namespace Microsoft.AspNet.Mvc
if (_actionExecutingContext.Result != null) if (_actionExecutingContext.Result != null)
{ {
// Short-circuited by setting a result. // Short-circuited by setting a result.
_actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters) _actionExecutedContext = new ActionExecutedContext(
_actionExecutingContext,
_filters,
Instance)
{ {
Canceled = true, Canceled = true,
Result = _actionExecutingContext.Result, Result = _actionExecutingContext.Result,
@ -277,7 +496,10 @@ namespace Microsoft.AspNet.Mvc
else else
{ {
// All action filters have run, execute the action method. // All action filters have run, execute the action method.
_actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters) _actionExecutedContext = new ActionExecutedContext(
_actionExecutingContext,
_filters,
Instance)
{ {
Result = await InvokeActionAsync(_actionExecutingContext), Result = await InvokeActionAsync(_actionExecutingContext),
}; };
@ -286,7 +508,10 @@ namespace Microsoft.AspNet.Mvc
catch (Exception exception) catch (Exception exception)
{ {
// Exceptions thrown by the action method OR filters bubble back up through ActionExcecutedContext. // 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) ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception)
}; };
@ -298,7 +523,7 @@ namespace Microsoft.AspNet.Mvc
{ {
_cursor.SetStage(FilterStage.ResultFilters); _cursor.SetStage(FilterStage.ResultFilters);
_resultExecutingContext = new ResultExecutingContext(ActionContext, _filters, result); _resultExecutingContext = new ResultExecutingContext(ActionContext, _filters, result, Instance);
await InvokeResultFilterAsync(); await InvokeResultFilterAsync();
Debug.Assert(_resultExecutingContext != null); Debug.Assert(_resultExecutingContext != null);
@ -325,7 +550,7 @@ namespace Microsoft.AspNet.Mvc
// This is forbidden. // This is forbidden.
var message = Resources.FormatAsyncResultFilter_InvalidShortCircuit( var message = Resources.FormatAsyncResultFilter_InvalidShortCircuit(
typeof(IAsyncResultFilter).Name, typeof(IAsyncResultFilter).Name,
"Cancel", nameof(ResultExecutingContext.Cancel),
typeof(ResultExecutingContext).Name, typeof(ResultExecutingContext).Name,
typeof(ResultExecutionDelegate).Name); typeof(ResultExecutionDelegate).Name);
@ -345,7 +570,8 @@ namespace Microsoft.AspNet.Mvc
_resultExecutedContext = new ResultExecutedContext( _resultExecutedContext = new ResultExecutedContext(
_resultExecutingContext, _resultExecutingContext,
_filters, _filters,
_resultExecutingContext.Result) _resultExecutingContext.Result,
Instance)
{ {
Canceled = true, Canceled = true,
}; };
@ -356,7 +582,8 @@ namespace Microsoft.AspNet.Mvc
_resultExecutedContext = new ResultExecutedContext( _resultExecutedContext = new ResultExecutedContext(
_resultExecutingContext, _resultExecutingContext,
_filters, _filters,
_resultExecutingContext.Result) _resultExecutingContext.Result,
Instance)
{ {
Canceled = true, Canceled = true,
}; };
@ -372,7 +599,8 @@ namespace Microsoft.AspNet.Mvc
_resultExecutedContext = new ResultExecutedContext( _resultExecutedContext = new ResultExecutedContext(
_resultExecutingContext, _resultExecutingContext,
_filters, _filters,
_resultExecutingContext.Result) _resultExecutingContext.Result,
Instance)
{ {
Canceled = true, Canceled = true,
}; };
@ -390,7 +618,8 @@ namespace Microsoft.AspNet.Mvc
_resultExecutedContext = new ResultExecutedContext( _resultExecutedContext = new ResultExecutedContext(
_resultExecutingContext, _resultExecutingContext,
_filters, _filters,
_resultExecutingContext.Result); _resultExecutingContext.Result,
Instance);
} }
} }
catch (Exception exception) catch (Exception exception)
@ -398,7 +627,8 @@ namespace Microsoft.AspNet.Mvc
_resultExecutedContext = new ResultExecutedContext( _resultExecutedContext = new ResultExecutedContext(
_resultExecutingContext, _resultExecutingContext,
_filters, _filters,
_resultExecutingContext.Result) _resultExecutingContext.Result,
Instance)
{ {
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception)
}; };
@ -423,8 +653,9 @@ namespace Microsoft.AspNet.Mvc
private enum FilterStage private enum FilterStage
{ {
Undefined, Undefined,
ExceptionFilters,
AuthorizationFilters, AuthorizationFilters,
ResourceFilters,
ExceptionFilters,
ActionFilters, ActionFilters,
ActionMethod, ActionMethod,
ResultFilters, ResultFilters,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -32,12 +32,6 @@ namespace Microsoft.AspNet.Mvc.Filters
} }
} }
var controllerFilter = context.ActionContext.Controller as IFilter;
if (controllerFilter != null)
{
InsertControllerAsFilter(context, controllerFilter);
}
if (callNext != null) if (callNext != null)
{ {
callNext(); 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) private void ApplyFilterToContainer(object actualFilter, IFilter filterMetadata)
{ {
Debug.Assert(actualFilter != null, "actualFilter should not be null"); Debug.Assert(actualFilter != null, "actualFilter should not be null");

View File

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

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