diff --git a/Mvc.sln b/Mvc.sln index 8f7bca4e31..1be43afbba 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -29,27 +29,23 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "MvcSample.Web", "samples\Mv EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Razor.Host", "src\Microsoft.AspNet.Mvc.Razor.Host\Microsoft.AspNet.Mvc.Razor.Host.kproj", "{520B3AA4-363A-497C-8C15-80423C5AFC85}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Razor.Host.Test", "test\Microsoft.AspNet.Mvc.Razor.Host.Test\Microsoft.AspNet.Mvc.Razor.Host.Test.kproj", "{7C4F5973-0491-4028-B1DC-A9BA73FF9F77}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "WebSites", "WebSites", "{16703B76-C9F7-4C75-AE6C-53D92E308E3C}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.FunctionalTests", "test\Microsoft.AspNet.Mvc.FunctionalTests\Microsoft.AspNet.Mvc.FunctionalTests.kproj", "{323D0C04-B518-4A8F-8A8E-3546AD153D34}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "BasicWebSite", "test\WebSites\BasicWebSite\BasicWebSite.kproj", "{34DF1487-12C6-476C-BE0A-F31DF1939AE5}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ActivatorWebSite", "test\WebSites\ActivatorWebSite\ActivatorWebSite.kproj", "{DB79BCBA-9538-4A53-87D9-77728E2BAA39}" -EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "InlineConstraintsWebSite", "test\WebSites\InlineConstraintsWebSite\InlineConstraintsWebSite.kproj", "{EA34877F-1AC1-42B7-B4E6-15A093F40CAE}" -EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "AutofacWebSite", "test\WebSites\AutofacWebSite\AutofacWebSite.kproj", "{07C0E921-FCBB-458C-AC11-3D01CE68B16B}" -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}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CompositeViewEngine", "test\WebSites\CompositeViewEngine\CompositeViewEngine.kproj", "{A853B2BA-4449-4908-A416-5A3C027FC22B}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RoutingWebSite", "test\WebSites\RoutingWebSite\RoutingWebSite.kproj", "{42CDBF4A-E238-4C0F-A416-44588363EB4C}" 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}") = "AutofacWebSite", "test\WebSites\AutofacWebSite\AutofacWebSite.kproj", "{07C0E921-FCBB-458C-AC11-3D01CE68B16B}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CompositeViewEngine", "test\WebSites\CompositeViewEngine\CompositeViewEngine.kproj", "{A853B2BA-4449-4908-A416-5A3C027FC22B}" +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "InlineConstraintsWebSite", "test\WebSites\InlineConstraintsWebSite\InlineConstraintsWebSite.kproj", "{EA34877F-1AC1-42B7-B4E6-15A093F40CAE}" +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 +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ActivatorWebSite", "test\WebSites\ActivatorWebSite\ActivatorWebSite.kproj", "{DB79BCBA-9538-4A53-87D9-77728E2BAA39}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorWebSite", "test\WebSites\RazorWebSite\RazorWebSite.kproj", "{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}" EndProject @@ -171,16 +167,6 @@ Global {520B3AA4-363A-497C-8C15-80423C5AFC85}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {520B3AA4-363A-497C-8C15-80423C5AFC85}.Release|Mixed Platforms.Build.0 = Release|Any CPU {520B3AA4-363A-497C-8C15-80423C5AFC85}.Release|x86.ActiveCfg = Release|Any CPU - {7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Debug|x86.ActiveCfg = Debug|Any CPU - {7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Release|Any CPU.Build.0 = Release|Any CPU - {7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {7C4F5973-0491-4028-B1DC-A9BA73FF9F77}.Release|x86.ActiveCfg = Release|Any CPU {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Debug|Any CPU.Build.0 = Debug|Any CPU {323D0C04-B518-4A8F-8A8E-3546AD153D34}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -201,66 +187,6 @@ Global {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Release|Mixed Platforms.Build.0 = Release|Any CPU {34DF1487-12C6-476C-BE0A-F31DF1939AE5}.Release|x86.ActiveCfg = Release|Any CPU - {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|x86.ActiveCfg = Debug|Any CPU - {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Any CPU.Build.0 = Release|Any CPU - {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|x86.ActiveCfg = Release|Any CPU - {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|x86.ActiveCfg = Debug|Any CPU - {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Release|Any CPU.Build.0 = Release|Any CPU - {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Release|x86.ActiveCfg = Release|Any CPU - {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Debug|x86.ActiveCfg = Debug|Any CPU - {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Release|Any CPU.Build.0 = Release|Any CPU - {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.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}.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 - {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|x86.ActiveCfg = Debug|Any CPU - {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Any CPU.Build.0 = Release|Any CPU - {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|x86.ActiveCfg = Release|Any CPU - {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {5F945B82-FE5F-425C-956C-8BC2F2020254}.Debug|x86.ActiveCfg = Debug|Any CPU - {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Any CPU.Build.0 = Release|Any CPU - {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|x86.ActiveCfg = Release|Any CPU {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 @@ -271,6 +197,56 @@ Global {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 + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Debug|x86.ActiveCfg = Debug|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Any CPU.Build.0 = Release|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {42CDBF4A-E238-4C0F-A416-44588363EB4C}.Release|x86.ActiveCfg = Release|Any CPU + {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Debug|x86.ActiveCfg = Debug|Any CPU + {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Release|Any CPU.Build.0 = Release|Any CPU + {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {07C0E921-FCBB-458C-AC11-3D01CE68B16B}.Release|x86.ActiveCfg = Release|Any CPU + {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Debug|x86.ActiveCfg = Debug|Any CPU + {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Release|Any CPU.Build.0 = Release|Any CPU + {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {EA34877F-1AC1-42B7-B4E6-15A093F40CAE}.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}.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 + {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Debug|x86.ActiveCfg = Debug|Any CPU + {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Any CPU.Build.0 = Release|Any CPU + {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {DB79BCBA-9538-4A53-87D9-77728E2BAA39}.Release|x86.ActiveCfg = Release|Any CPU {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Any CPU.Build.0 = Debug|Any CPU {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -336,17 +312,9 @@ Global {A8AA326E-8EE8-4F11-B750-23028E0949D7} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {FBB2B86E-972B-4185-9FF2-62CAB5F8388F} = {DAAE4C74-D06F-4874-A166-33305D2643CE} {520B3AA4-363A-497C-8C15-80423C5AFC85} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} - {7C4F5973-0491-4028-B1DC-A9BA73FF9F77} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {16703B76-C9F7-4C75-AE6C-53D92E308E3C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {323D0C04-B518-4A8F-8A8E-3546AD153D34} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {34DF1487-12C6-476C-BE0A-F31DF1939AE5} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} - {DB79BCBA-9538-4A53-87D9-77728E2BAA39} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} - {EA34877F-1AC1-42B7-B4E6-15A093F40CAE} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} - {07C0E921-FCBB-458C-AC11-3D01CE68B16B} = {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} - {5F945B82-FE5F-425C-956C-8BC2F2020254} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} - {A853B2BA-4449-4908-A416-5A3C027FC22B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {B07CAF59-11ED-40E3-A5DB-E1178F84FA78} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {62735776-46FF-4170-9392-02E128A69B89} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {14F79E79-AE79-48FA-95DE-D794EF4EABB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultActionSelector.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultActionSelector.cs index 956bb9d4ae..6bf5963976 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultActionSelector.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultActionSelector.cs @@ -3,12 +3,13 @@ using System; using System.Collections.Generic; -using System.Diagnostics.Contracts; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Core; +using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Routing; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc { @@ -16,60 +17,108 @@ namespace Microsoft.AspNet.Mvc { private readonly IActionDescriptorsCollectionProvider _actionDescriptorsCollectionProvider; private readonly IActionBindingContextProvider _bindingProvider; + private ILogger _logger; public DefaultActionSelector(IActionDescriptorsCollectionProvider actionDescriptorsCollectionProvider, - IActionBindingContextProvider bindingProvider) + IActionBindingContextProvider bindingProvider, + [NotNull] ILoggerFactory loggerFactory) { _actionDescriptorsCollectionProvider = actionDescriptorsCollectionProvider; _bindingProvider = bindingProvider; + _logger = loggerFactory.Create(); } public async Task SelectAsync([NotNull] RouteContext context) { - var allDescriptors = GetActions(); - - var matching = allDescriptors.Where(ad => Match(ad, context)).ToList(); - - var matchesWithConstraints = new List(); - foreach (var match in matching) + using (_logger.BeginScope("DefaultActionSelector.SelectAsync")) { - if (match.DynamicConstraints != null && match.DynamicConstraints.Any() || - match.MethodConstraints != null && match.MethodConstraints.Any()) + var allDescriptors = GetActions(); + + var matchingRouteConstraints = + allDescriptors.Where(ad => + MatchRouteConstraints(ad, context)).ToList(); + + var matchingRouteAndMethodConstraints = + matchingRouteConstraints.Where(ad => + MatchMethodConstraints(ad, context)).ToList(); + + var matchingRouteAndMethodAndDynamicConstraints = + matchingRouteAndMethodConstraints.Where(ad => + MatchDynamicConstraints(ad, context)).ToList(); + + var matching = matchingRouteAndMethodAndDynamicConstraints; + + var matchesWithConstraints = new List(); + foreach (var match in matching) { - matchesWithConstraints.Add(match); + if (match.DynamicConstraints != null && match.DynamicConstraints.Any() || + match.MethodConstraints != null && match.MethodConstraints.Any()) + { + matchesWithConstraints.Add(match); + } } - } - // If any action that's applicable has constraints, this is considered better than - // an action without. - if (matchesWithConstraints.Any()) - { - matching = matchesWithConstraints; - } + // If any action that's applicable has constraints, this is considered better than + // an action without. + if (matchesWithConstraints.Any()) + { + matching = matchesWithConstraints; + } - if (matching.Count == 0) - { - return null; - } - else - { - return await SelectBestCandidate(context, matching); + if (matching.Count == 0) + { + if (_logger.IsEnabled(TraceType.Information)) + { + _logger.WriteValues(new DefaultActionSelectorSelectAsyncValues() + { + ActionsMatchingRouteConstraints = matchingRouteConstraints, + ActionsMatchingRouteAndMethodConstraints = matchingRouteAndMethodConstraints, + ActionsMatchingRouteAndMethodAndDynamicConstraints = + matchingRouteAndMethodAndDynamicConstraints, + ActionsMatchingWithConstraints = matchesWithConstraints + }); + } + + return null; + } + else + { + var selectedAction = await SelectBestCandidate(context, matching); + + if (_logger.IsEnabled(TraceType.Information)) + { + _logger.WriteValues(new DefaultActionSelectorSelectAsyncValues() + { + ActionsMatchingRouteConstraints = matchingRouteConstraints, + ActionsMatchingRouteAndMethodConstraints = matchingRouteAndMethodConstraints, + ActionsMatchingRouteAndMethodAndDynamicConstraints = + matchingRouteAndMethodAndDynamicConstraints, + ActionsMatchingWithConstraints = matchesWithConstraints, + SelectedAction = selectedAction + }); + } + + return selectedAction; + } } } - public bool Match(ActionDescriptor descriptor, RouteContext context) + private bool MatchRouteConstraints(ActionDescriptor descriptor, RouteContext context) { - if (descriptor == null) - { - throw new ArgumentNullException("descriptor"); - } + return descriptor.RouteConstraints == null || + descriptor.RouteConstraints.All(c => c.Accept(context)); + } - return (descriptor.RouteConstraints == null || - descriptor.RouteConstraints.All(c => c.Accept(context))) && - (descriptor.MethodConstraints == null || - descriptor.MethodConstraints.All(c => c.Accept(context))) && - (descriptor.DynamicConstraints == null || - descriptor.DynamicConstraints.All(c => c.Accept(context))); + private bool MatchMethodConstraints(ActionDescriptor descriptor, RouteContext context) + { + return descriptor.MethodConstraints == null || + descriptor.MethodConstraints.All(c => c.Accept(context)); + } + + private bool MatchDynamicConstraints(ActionDescriptor descriptor, RouteContext context) + { + return descriptor.DynamicConstraints == null || + descriptor.DynamicConstraints.All(c => c.Accept(context)); } protected virtual async Task SelectBestCandidate( diff --git a/src/Microsoft.AspNet.Mvc.Core/IActionSelector.cs b/src/Microsoft.AspNet.Mvc.Core/IActionSelector.cs index 170dfbd050..0678c18f72 100644 --- a/src/Microsoft.AspNet.Mvc.Core/IActionSelector.cs +++ b/src/Microsoft.AspNet.Mvc.Core/IActionSelector.cs @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNet.Routing; @@ -11,8 +10,6 @@ namespace Microsoft.AspNet.Mvc { Task SelectAsync(RouteContext context); - bool Match(ActionDescriptor descriptor, RouteContext context); - bool HasValidAction(VirtualPathContext context); } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteRouteAsyncValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteRouteAsyncValues.cs new file mode 100644 index 0000000000..3952f04e00 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteRouteAsyncValues.cs @@ -0,0 +1,61 @@ +// 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 System.Text; +using Microsoft.AspNet.Routing.Template; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Represents the state of . + /// + public class AttributeRouteRouteAsyncValues + { + /// + /// The name of the state. + /// + public string Name + { + get + { + return "AttributeRoute.RouteAsync"; + } + } + + /// + /// The matching routes. + /// + public IList MatchingRoutes { get; set; } + + /// + /// True if the request is handled. + /// + public bool Handled { get; set; } + + /// + /// A summary of the data for display. + /// + public string Summary + { + get + { + var builder = new StringBuilder(); + builder.AppendLine(Name); + builder.Append("\tMatching routes: "); + StringBuilderHelpers.Append(builder, MatchingRoutes); + builder.AppendLine(); + builder.Append("\tHandled? "); + builder.Append(Handled); + return builder.ToString(); + } + } + + /// + public override string ToString() + { + return Summary; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/DefaultActionSelectorSelectAsyncValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/DefaultActionSelectorSelectAsyncValues.cs new file mode 100644 index 0000000000..72dcc1bb6f --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/DefaultActionSelectorSelectAsyncValues.cs @@ -0,0 +1,88 @@ +// 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 System.Text; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Represents the state of . + /// + public class DefaultActionSelectorSelectAsyncValues + { + /// + /// The name of the state. + /// + public string Name + { + get + { + return "DefaultActionSelector.SelectAsync"; + } + } + + /// + /// The list of actions that matched all their route constraints, if any. + /// + public IList ActionsMatchingRouteConstraints { get; set; } + + /// + /// The list of actions that matched all their route and method constraints, if any. + /// + public IList ActionsMatchingRouteAndMethodConstraints { get; set; } + + /// + /// The list of actions that matched all their route, method, and dynamic constraints, if any. + /// + public IList ActionsMatchingRouteAndMethodAndDynamicConstraints { get; set; } + + /// + /// The actions that matched with at least one constraint. + /// + public IList ActionsMatchingWithConstraints { get; set; } + + /// + /// The selected action. + /// + public ActionDescriptor SelectedAction { get; set; } + + /// + /// A summary of the data for display. + /// + public string Summary + { + get + { + var builder = new StringBuilder(); + builder.AppendLine(Name); + builder.Append("\tActions matching route constraints: "); + StringBuilderHelpers.Append(builder, ActionsMatchingRouteConstraints, Formatter); + builder.AppendLine(); + builder.Append("\tActions matching route and method constraints: "); + StringBuilderHelpers.Append(builder, ActionsMatchingRouteAndMethodConstraints, Formatter); + builder.AppendLine(); + builder.Append("\tActions matching route, method, and dynamic constraints: "); + StringBuilderHelpers.Append(builder, ActionsMatchingRouteAndMethodAndDynamicConstraints, Formatter); + builder.AppendLine(); + builder.Append("\tActions matching with at least one constraint: "); + StringBuilderHelpers.Append(builder, ActionsMatchingWithConstraints, Formatter); + builder.AppendLine(); + builder.Append("\tSelected action: "); + builder.Append(Formatter(SelectedAction)); + return builder.ToString(); + } + } + + /// + public override string ToString() + { + return Summary; + } + + private string Formatter(ActionDescriptor descriptor) + { + return descriptor.DisplayName; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/LogFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/LogFormatter.cs new file mode 100644 index 0000000000..ac280abfb7 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/LogFormatter.cs @@ -0,0 +1,37 @@ +// 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.Logging +{ + public static class LogFormatter + { + /// + /// A formatter for use with . + /// + public static string Formatter(object o, Exception e) + { + if (o != null && e != null) + { + return o + Environment.NewLine + e; + } + + if (o != null) + { + return o.ToString(); + } + + if (e != null) + { + return e.ToString(); + } + + return ""; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/LoggerExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/LoggerExtensions.cs new file mode 100644 index 0000000000..fe576dcd79 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/LoggerExtensions.cs @@ -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.Framework.Logging; + +namespace Microsoft.AspNet.Mvc.Logging +{ + internal static class LoggerExtensions + { + public static bool WriteValues([NotNull] this ILogger logger, object values) + { + return logger.WriteCore( + eventType: TraceType.Information, + eventId: 0, + state: values, + exception: null, + formatter: LogFormatter.Formatter); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/MvcRouteHandlerRouteAsyncValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/MvcRouteHandlerRouteAsyncValues.cs new file mode 100644 index 0000000000..6b135739b6 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/MvcRouteHandlerRouteAsyncValues.cs @@ -0,0 +1,66 @@ +// 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.Text; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Represents the state of . + /// + public class MvcRouteHandlerRouteAsyncValues + { + /// + /// The name of the state. + /// + public string Name + { + get + { + return "MvcRouteHandler.RouteAsync"; + } + } + + /// + /// True if an action was selected. + /// + public bool ActionSelected { get; set; } + + /// + /// True if the selected action was invoked. + /// + public bool ActionInvoked { get; set; } + + /// + /// True if the request is handled. + /// + public bool Handled { get; set; } + + /// + /// A summary of the data for display. + /// + public string Summary + { + get + { + var builder = new StringBuilder(); + builder.AppendLine(Name); + builder.Append("\tAction selected? "); + builder.Append(ActionSelected); + builder.AppendLine(); + builder.Append("\tAction invoked? "); + builder.Append(ActionInvoked); + builder.AppendLine(); + builder.Append("\tHandled? "); + builder.Append(Handled); + return builder.ToString(); + } + } + + /// + public override string ToString() + { + return Summary; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/StringBuilderHelpers.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/StringBuilderHelpers.cs new file mode 100644 index 0000000000..725ef8a63e --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/StringBuilderHelpers.cs @@ -0,0 +1,55 @@ +// 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.Text; + +namespace Microsoft.AspNet.Mvc.Logging +{ + internal static class StringBuilderHelpers + { + public static void Append( + StringBuilder builder, + IEnumerable items, + Func itemFormatter = null) + { + if (items == null) + { + return; + } + + foreach (var item in items) + { + builder.Append(Environment.NewLine); + builder.Append("\t\t"); + + if (itemFormatter == null) + { + builder.Append(item != null ? item.ToString() : "null"); + } + else + { + builder.Append(item != null ? itemFormatter(item) : "null"); + } + } + } + + public static void Append(StringBuilder builder, IDictionary dict) + { + if (dict == null) + { + return; + } + + foreach (var kvp in dict) + { + builder.Append(Environment.NewLine); + builder.Append("\t\t"); + builder.Append(kvp.Key != null ? kvp.Key.ToString() : "null"); + builder.Append(" : "); + builder.Append(kvp.Value != null ? kvp.Value.ToString() : "null"); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj index 7c307edad4..2735d502b4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj +++ b/src/Microsoft.AspNet.Mvc.Core/Microsoft.AspNet.Mvc.Core.kproj @@ -31,6 +31,10 @@ + + + + @@ -44,11 +48,9 @@ - - @@ -56,12 +58,6 @@ - - - - - - @@ -103,8 +99,10 @@ + + @@ -163,11 +161,14 @@ - + + + + @@ -179,9 +180,15 @@ + + + + + + @@ -227,16 +234,15 @@ + - - diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcRouteHandler.cs b/src/Microsoft.AspNet.Mvc.Core/MvcRouteHandler.cs index 0cc1cf8ff6..d248f16fb0 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcRouteHandler.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcRouteHandler.cs @@ -4,14 +4,19 @@ using System; using System.Diagnostics.Contracts; using System.Threading.Tasks; +using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.Core; +using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc { public class MvcRouteHandler : IRouter { + private ILogger _logger; + public string GetVirtualPath([NotNull] VirtualPathContext context) { // The contract of this method is to check that the values coming in from the route are valid; @@ -31,12 +36,27 @@ namespace Microsoft.AspNet.Mvc // TODO: Throw an error here that's descriptive enough so that // users understand they should call the per request scoped middleware // or set HttpContext.Services manually - var actionSelector = services.GetService(); - var actionDescriptor = await actionSelector.SelectAsync(context); - if (actionDescriptor == null) + + EnsureLogger(context.HttpContext); + using (_logger.BeginScope("MvcRouteHandler.RouteAsync")) { - return; - } + var actionSelector = services.GetService(); + var actionDescriptor = await actionSelector.SelectAsync(context); + + if (actionDescriptor == null) + { + if (_logger.IsEnabled(TraceType.Information)) + { + _logger.WriteValues(new MvcRouteHandlerRouteAsyncValues() + { + ActionSelected = false, + ActionInvoked = false, + Handled = context.IsHandled + }); + } + + return; + } if (actionDescriptor.RouteValueDefaults != null) { @@ -49,28 +69,49 @@ namespace Microsoft.AspNet.Mvc } } - var actionContext = new ActionContext(context.HttpContext, context.RouteData, actionDescriptor); + var actionContext = new ActionContext(context.HttpContext, context.RouteData, actionDescriptor); - var contextAccessor = services.GetService>(); - using (contextAccessor.SetContextSource(() => actionContext, PreventExchange)) - { - var invokerFactory = services.GetService(); - var invoker = invokerFactory.CreateInvoker(actionContext); - if (invoker == null) + var contextAccessor = services.GetService>(); + using (contextAccessor.SetContextSource(() => actionContext, PreventExchange)) { - var ex = new InvalidOperationException( - Resources.FormatActionInvokerFactory_CouldNotCreateInvoker(actionDescriptor)); + var invokerFactory = services.GetService(); + var invoker = invokerFactory.CreateInvoker(actionContext); + if (invoker == null) + { + var ex = new InvalidOperationException( + Resources.FormatActionInvokerFactory_CouldNotCreateInvoker( + actionDescriptor.DisplayName)); - // Add tracing/logging (what do we think of this pattern of - // tacking on extra data on the exception?) - ex.Data.Add("AD", actionDescriptor); + // Add tracing/logging (what do we think of this pattern of + // tacking on extra data on the exception?) + ex.Data.Add("AD", actionDescriptor); - throw ex; + if (_logger.IsEnabled(TraceType.Information)) + { + _logger.WriteValues(new MvcRouteHandlerRouteAsyncValues() + { + ActionSelected = true, + ActionInvoked = false, + Handled = context.IsHandled + }); + } + + throw ex; + } + + await invoker.InvokeActionAsync(); + context.IsHandled = true; + + if (_logger.IsEnabled(TraceType.Information)) + { + _logger.WriteValues(new MvcRouteHandlerRouteAsyncValues() + { + ActionSelected = true, + ActionInvoked = true, + Handled = context.IsHandled + }); + } } - - await invoker.InvokeActionAsync(); - - context.IsHandled = true; } } @@ -78,5 +119,14 @@ namespace Microsoft.AspNet.Mvc { throw new InvalidOperationException(Resources.ActionContextAccessor_SetValueNotSupported); } + + private void EnsureLogger(HttpContext context) + { + if (_logger == null) + { + var factory = context.RequestServices.GetService(); + _logger = factory.Create(); + } + } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs index e8bf4b2635..f4ffe7d273 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs @@ -5,8 +5,10 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Template; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc.Routing { @@ -18,6 +20,8 @@ namespace Microsoft.AspNet.Mvc.Routing private readonly IRouter _next; private readonly TemplateRoute[] _matchingRoutes; private readonly AttributeRouteLinkGenerationEntry[] _linkGenerationEntries; + private ILogger _logger; + private ILogger _constraintLogger; /// /// Creates a new . @@ -25,9 +29,10 @@ namespace Microsoft.AspNet.Mvc.Routing /// The next router. Invoked when a route entry matches. /// The set of route entries. public AttributeRoute( - [NotNull] IRouter next, + [NotNull] IRouter next, [NotNull] IEnumerable matchingEntries, - [NotNull] IEnumerable linkGenerationEntries) + [NotNull] IEnumerable linkGenerationEntries, + [NotNull] ILoggerFactory factory) { _next = next; @@ -38,17 +43,33 @@ namespace Microsoft.AspNet.Mvc.Routing // FOR RIGHT NOW - this is just an array of entries. We'll follow up by implementing // a good data-structure here. See #741 _linkGenerationEntries = linkGenerationEntries.OrderBy(e => e.Precedence).ToArray(); + + _logger = factory.Create(); + _constraintLogger = factory.Create(typeof(RouteConstraintMatcher).FullName); } /// public async Task RouteAsync([NotNull] RouteContext context) { - foreach (var route in _matchingRoutes) + using (_logger.BeginScope("AttributeRoute.RouteAsync")) { - await route.RouteAsync(context); - if (context.IsHandled) + foreach (var route in _matchingRoutes) { - return; + await route.RouteAsync(context); + + if (context.IsHandled) + { + break; + } + } + + if (_logger.IsEnabled(TraceType.Information)) + { + _logger.WriteValues(new AttributeRouteRouteAsyncValues() + { + MatchingRoutes = _matchingRoutes, + Handled = context.IsHandled + }); } } } @@ -130,7 +151,9 @@ namespace Microsoft.AspNet.Mvc.Routing bindingResult.CombinedValues, context.Context, this, - RouteDirection.UrlGeneration); + RouteDirection.UrlGeneration, + _constraintLogger); + if (!matched) { // A constraint rejected this link. diff --git a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouting.cs b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouting.cs index 7107e9646f..c3122548c3 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouting.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRouting.cs @@ -8,6 +8,7 @@ using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Template; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc.Routing { @@ -69,7 +70,11 @@ namespace Microsoft.AspNet.Mvc.Routing }); } - return new AttributeRoute(target, matchingEntries, generationEntries); + return new AttributeRoute( + target, + matchingEntries, + generationEntries, + services.GetService()); } private static IReadOnlyList GetActionDescriptors(IServiceProvider services) diff --git a/src/Microsoft.AspNet.Mvc.Core/project.json b/src/Microsoft.AspNet.Mvc.Core/project.json index 2cd566fa99..11b4ddf987 100644 --- a/src/Microsoft.AspNet.Mvc.Core/project.json +++ b/src/Microsoft.AspNet.Mvc.Core/project.json @@ -14,6 +14,7 @@ "Microsoft.AspNet.Security.DataProtection" : "1.0.0-*", "Microsoft.Framework.DependencyInjection": "1.0.0-*", "Microsoft.Framework.Runtime.Interfaces": "1.0.0-*", + "Microsoft.Framework.Logging": "1.0.0-*", "Microsoft.Framework.OptionsModel": "1.0.0-*", "Newtonsoft.Json": "5.0.8" }, diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionAttributeTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionAttributeTests.cs index 709b1e3a70..5cda802b73 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionAttributeTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionAttributeTests.cs @@ -16,7 +16,7 @@ using Microsoft.Framework.DependencyInjection.NestedProviders; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc.Test +namespace Microsoft.AspNet.Mvc { public class ActionAttributeTests { @@ -199,7 +199,8 @@ namespace Microsoft.AspNet.Mvc.Test var bindingProvider = new Mock(); var defaultActionSelector = new DefaultActionSelector(actionCollectionDescriptorProvider, - bindingProvider.Object); + bindingProvider.Object, + NullLoggerFactory.Instance); return await defaultActionSelector.SelectAsync(context); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDiscoveryConventionsActionSelectionTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDiscoveryConventionsActionSelectionTests.cs index 35fdf48cc4..aff4c35210 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDiscoveryConventionsActionSelectionTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDiscoveryConventionsActionSelectionTests.cs @@ -16,7 +16,7 @@ using Microsoft.Framework.DependencyInjection.NestedProviders; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc.Test +namespace Microsoft.AspNet.Mvc { public class DefaultActionDiscoveryConventionsActionSelectionTests { @@ -178,7 +178,8 @@ namespace Microsoft.AspNet.Mvc.Test var bindingProvider = new Mock(); var defaultActionSelector = new DefaultActionSelector(actionCollectionDescriptorProvider, - bindingProvider.Object); + bindingProvider.Object, + NullLoggerFactory.Instance); return await defaultActionSelector.SelectAsync(context); } @@ -202,7 +203,6 @@ namespace Microsoft.AspNet.Mvc.Test var headers = new Mock(); request.SetupGet(r => r.Headers).Returns(headers.Object); request.SetupGet(x => x.Method).Returns(httpMethod); - var httpContext = new Mock(); httpContext.SetupGet(c => c.Request).Returns(request.Object); return httpContext.Object; diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs similarity index 60% rename from test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTest.cs rename to test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs index c314f19e9e..d7de36724a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs @@ -7,13 +7,110 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Routing; +using Microsoft.AspNet.Mvc.Logging; +using Microsoft.Framework.Logging; using Moq; using Xunit; -namespace Microsoft.AspNet.Mvc.Core.Test +namespace Microsoft.AspNet.Mvc { - public class DefaultActionSelectorTest + public class DefaultActionSelectorTests { + [Fact] + public async void SelectAsync_NoMatchedActions_LogIsCorrect() + { + // Arrange + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink); + + var routeContext = CreateRouteContext("POST"); + + var actions = new ActionDescriptor[0]; + var selector = CreateSelector(actions, loggerFactory); + + // Act + var action = await selector.SelectAsync(routeContext); + + // Assert + Assert.Equal(1, sink.Scopes.Count); + var scope = sink.Scopes[0]; + Assert.Equal(typeof(DefaultActionSelector).FullName, scope.LoggerName); + Assert.Equal("DefaultActionSelector.SelectAsync", scope.Scope); + + // There is a record for IsEnabled and one for WriteCore. + Assert.Equal(2, sink.Writes.Count); + + var enabled = sink.Writes[0]; + Assert.Equal(typeof(DefaultActionSelector).FullName, enabled.LoggerName); + Assert.Equal("DefaultActionSelector.SelectAsync", enabled.Scope); + Assert.Null(enabled.State); + + var write = sink.Writes[1]; + Assert.Equal(typeof(DefaultActionSelector).FullName, write.LoggerName); + Assert.Equal("DefaultActionSelector.SelectAsync", write.Scope); + var values = Assert.IsType(write.State); + Assert.Equal("DefaultActionSelector.SelectAsync", values.Name); + Assert.Empty(values.ActionsMatchingRouteConstraints); + Assert.Empty(values.ActionsMatchingRouteAndMethodConstraints); + Assert.Empty(values.ActionsMatchingRouteAndMethodAndDynamicConstraints); + Assert.Empty(values.ActionsMatchingWithConstraints); + Assert.Null(values.SelectedAction); + } + + [Fact] + public async void SelectAsync_MatchedActions_LogIsCorrect() + { + // Arrange + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink); + + var matched = new ActionDescriptor() + { + MethodConstraints = new List() + { + new HttpMethodConstraint(new string[] { "POST" }), + }, + Parameters = new List(), + }; + + var notMatched = new ActionDescriptor() + { + Parameters = new List(), + }; + + var actions = new ActionDescriptor[] { matched, notMatched }; + var selector = CreateSelector(actions, loggerFactory); + + var routeContext = CreateRouteContext("POST"); + + // Act + var action = await selector.SelectAsync(routeContext); + + // Assert + Assert.Equal(1, sink.Scopes.Count); + var scope = sink.Scopes[0]; + Assert.Equal(typeof(DefaultActionSelector).FullName, scope.LoggerName); + Assert.Equal("DefaultActionSelector.SelectAsync", scope.Scope); + + // There is a record for IsEnabled and one for WriteCore. + Assert.Equal(2, sink.Writes.Count); + + var enabled = sink.Writes[0]; + Assert.Equal(typeof(DefaultActionSelector).FullName, enabled.LoggerName); + Assert.Equal("DefaultActionSelector.SelectAsync", enabled.Scope); + Assert.Null(enabled.State); + + var write = sink.Writes[1]; + Assert.Equal(typeof(DefaultActionSelector).FullName, write.LoggerName); + Assert.Equal("DefaultActionSelector.SelectAsync", write.Scope); + var values = Assert.IsType(write.State); + Assert.Equal("DefaultActionSelector.SelectAsync", values.Name); + Assert.NotEmpty(values.ActionsMatchingRouteConstraints); + Assert.NotEmpty(values.ActionsMatchingRouteAndMethodConstraints); + Assert.NotEmpty(values.ActionsMatchingWithConstraints); + Assert.Equal(matched, values.SelectedAction); + } + [Fact] public void HasValidAction_Match() { @@ -22,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test var selector = CreateSelector(actions); var context = CreateContext(new { }); - context.ProvidedValues = new RouteValueDictionary(new { controller = "Home", action = "Index"}); + context.ProvidedValues = new RouteValueDictionary(new { controller = "Home", action = "Index" }); // Act var isValid = selector.HasValidAction(context); @@ -109,8 +206,10 @@ namespace Microsoft.AspNet.Mvc.Core.Test .Where(a => a.RouteConstraints.Any(c => c.RouteKey == "action" && c.Comparer.Equals(c.RouteValue, action))); } - private static DefaultActionSelector CreateSelector(IReadOnlyList actions) + private static DefaultActionSelector CreateSelector(IReadOnlyList actions, ILoggerFactory loggerFactory = null) { + loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; + var actionProvider = new Mock(MockBehavior.Strict); actionProvider @@ -121,7 +220,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test .Setup(bp => bp.GetActionBindingContextAsync(It.IsAny())) .Returns(() => Task.FromResult(null)); - return new DefaultActionSelector(actionProvider.Object, bindingProvider.Object); + return new DefaultActionSelector(actionProvider.Object, bindingProvider.Object, loggerFactory); } private static VirtualPathContext CreateContext(object routeValues) @@ -145,7 +244,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test }; routeData.Routers.Add(new Mock(MockBehavior.Strict).Object); - + var httpContext = new Mock(MockBehavior.Strict); var request = new Mock(MockBehavior.Strict); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Logging/BeginScopeContext.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/BeginScopeContext.cs new file mode 100644 index 0000000000..4c69db10b8 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/BeginScopeContext.cs @@ -0,0 +1,12 @@ +// 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 +{ + public class BeginScopeContext + { + public object Scope { get; set; } + + public string LoggerName { get; set; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Logging/NullDisposable.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/NullDisposable.cs new file mode 100644 index 0000000000..7d2e1ef73a --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/NullDisposable.cs @@ -0,0 +1,17 @@ +// 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 +{ + public class NullDisposable : IDisposable + { + public static NullDisposable Instance = new NullDisposable(); + + public void Dispose() + { + // intentionally does nothing + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Logging/NullLogger.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/NullLogger.cs new file mode 100644 index 0000000000..af228397f8 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/NullLogger.cs @@ -0,0 +1,23 @@ +// 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 Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc +{ + public class NullLogger : ILogger + { + public static NullLogger Instance = new NullLogger(); + + public IDisposable BeginScope(object state) + { + return NullDisposable.Instance; + } + + public bool WriteCore(TraceType eventType, int eventId, object state, Exception exception, Func formatter) + { + return false; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Logging/NullLoggerFactory.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/NullLoggerFactory.cs new file mode 100644 index 0000000000..61b3dc076e --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/NullLoggerFactory.cs @@ -0,0 +1,17 @@ +// 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.Framework.Logging; + +namespace Microsoft.AspNet.Mvc +{ + public class NullLoggerFactory : ILoggerFactory + { + public static NullLoggerFactory Instance = new NullLoggerFactory(); + + public ILogger Create(string name) + { + return NullLogger.Instance; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Logging/TestLogger.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/TestLogger.cs new file mode 100644 index 0000000000..8bf8301ae4 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/TestLogger.cs @@ -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 Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc +{ + public class TestLogger : ILogger + { + private object _scope; + private TestSink _sink; + private string _name; + + public TestLogger(string name, TestSink sink) + { + _sink = sink; + _name = name; + } + + public string Name { get; set; } + + public IDisposable BeginScope(object state) + { + _scope = state; + + _sink.Begin(new BeginScopeContext() + { + LoggerName = _name, + Scope = state, + }); + + return NullDisposable.Instance; + } + + public bool WriteCore(TraceType eventType, int eventId, object state, Exception exception, Func formatter) + { + _sink.Write(new WriteCoreContext() + { + EventType = eventType, + EventId = eventId, + State = state, + Exception = exception, + Formatter = formatter, + LoggerName = _name, + Scope = _scope + }); + + return true; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Logging/TestLoggerFactory.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/TestLoggerFactory.cs new file mode 100644 index 0000000000..95633d1410 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/TestLoggerFactory.cs @@ -0,0 +1,22 @@ +// 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.Framework.Logging; + +namespace Microsoft.AspNet.Mvc +{ + public class TestLoggerFactory : ILoggerFactory + { + private TestSink _sink; + + public TestLoggerFactory(TestSink sink) + { + _sink = sink; + } + + public ILogger Create(string name) + { + return new TestLogger(name, _sink); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Logging/TestSink.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/TestSink.cs new file mode 100644 index 0000000000..29d48c3143 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/TestSink.cs @@ -0,0 +1,56 @@ +// 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; + +namespace Microsoft.AspNet.Mvc +{ + public class TestSink + { + public TestSink( + Func writeEnabled = null, + Func beginEnabled = null) + { + WriteEnabled = writeEnabled; + BeginEnabled = beginEnabled; + + Scopes = new List(); + Writes = new List(); + } + + public Func WriteEnabled { get; set; } + + public Func BeginEnabled { get; set; } + + public List Scopes { get; set; } + + public List Writes { get; set; } + + public void Write(WriteCoreContext context) + { + if (WriteEnabled == null || WriteEnabled(context)) + { + Writes.Add(context); + } + } + + public void Begin(BeginScopeContext context) + { + if (BeginEnabled == null || BeginEnabled(context)) + { + Scopes.Add(context); + } + } + + public static bool EnableWithTypeName(WriteCoreContext context) + { + return context.LoggerName.Equals(typeof(T).FullName); + } + + public static bool EnableWithTypeName(BeginScopeContext context) + { + return context.LoggerName.Equals(typeof(T).FullName); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Logging/WriteCoreContext.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/WriteCoreContext.cs new file mode 100644 index 0000000000..838aac7af8 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/WriteCoreContext.cs @@ -0,0 +1,25 @@ +// 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 Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc +{ + public class WriteCoreContext + { + public TraceType EventType { get; set; } + + public int EventId { get; set; } + + public object State { get; set; } + + public Exception Exception { get; set; } + + public Func Formatter { get; set; } + + public object Scope { get; set; } + + public string LoggerName { get; set; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj index df13c877bc..0c51eb1351 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Microsoft.AspNet.Mvc.Core.Test.kproj @@ -25,14 +25,22 @@ - + + - - + + + + + + + + + @@ -44,20 +52,16 @@ - - - - - - - + + + @@ -69,19 +73,25 @@ - + + + + + + + - + @@ -96,10 +106,9 @@ - - + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/MockMvcOptionsAccessor.cs b/test/Microsoft.AspNet.Mvc.Core.Test/MockMvcOptionsAccessor.cs index 5b1a212300..47f936194f 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/MockMvcOptionsAccessor.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/MockMvcOptionsAccessor.cs @@ -3,7 +3,7 @@ using Microsoft.Framework.OptionsModel; -namespace Microsoft.AspNet.Mvc.Test +namespace Microsoft.AspNet.Mvc { public class MockMvcOptionsAccessor : IOptionsAccessor { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/MvcRouteHandlerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/MvcRouteHandlerTests.cs new file mode 100644 index 0000000000..794445bd2d --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/MvcRouteHandlerTests.cs @@ -0,0 +1,199 @@ +// 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.Http; +using Microsoft.AspNet.Mvc.Logging; +using Microsoft.AspNet.Routing; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc +{ + public class MvcRouteHandlerTests + { + [Fact] + public async void RouteAsync_Success_LogsCorrectValues() + { + // Arrange + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink); + + var context = CreateRouteContext(loggerFactory: loggerFactory); + + var handler = new MvcRouteHandler(); + + // Act + await handler.RouteAsync(context); + + // Assert + Assert.Equal(1, sink.Scopes.Count); + var scope = sink.Scopes[0]; + Assert.Equal(typeof(MvcRouteHandler).FullName, scope.LoggerName); + Assert.Equal("MvcRouteHandler.RouteAsync", scope.Scope); + + // There is a record for IsEnabled and one for WriteCore. + Assert.Equal(2, sink.Writes.Count); + + var enabled = sink.Writes[0]; + Assert.Equal(typeof(MvcRouteHandler).FullName, enabled.LoggerName); + Assert.Equal("MvcRouteHandler.RouteAsync", enabled.Scope); + Assert.Null(enabled.State); + + var write = sink.Writes[1]; + Assert.Equal(typeof(MvcRouteHandler).FullName, write.LoggerName); + Assert.Equal("MvcRouteHandler.RouteAsync", write.Scope); + var values = Assert.IsType(write.State); + Assert.Equal("MvcRouteHandler.RouteAsync", values.Name); + Assert.Equal(true, values.ActionSelected); + Assert.Equal(true, values.ActionInvoked); + Assert.Equal(true, values.Handled); + } + + [Fact] + public async void RouteAsync_FailOnNoAction_LogsCorrectValues() + { + // Arrange + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink); + + var mockActionSelector = new Mock(); + mockActionSelector.Setup(a => a.SelectAsync(It.IsAny())) + .Returns(Task.FromResult(null)); + + var context = CreateRouteContext( + actionSelector: mockActionSelector.Object, + loggerFactory: loggerFactory); + + var handler = new MvcRouteHandler(); + + // Act + await handler.RouteAsync(context); + + // Assert + Assert.Equal(1, sink.Scopes.Count); + var scope = sink.Scopes[0]; + Assert.Equal(typeof(MvcRouteHandler).FullName, scope.LoggerName); + Assert.Equal("MvcRouteHandler.RouteAsync", scope.Scope); + + // There is a record for IsEnabled and one for WriteCore. + Assert.Equal(2, sink.Writes.Count); + + var enabled = sink.Writes[0]; + Assert.Equal(typeof(MvcRouteHandler).FullName, enabled.LoggerName); + Assert.Equal("MvcRouteHandler.RouteAsync", enabled.Scope); + Assert.Null(enabled.State); + + var write = sink.Writes[1]; + Assert.Equal(typeof(MvcRouteHandler).FullName, write.LoggerName); + Assert.Equal("MvcRouteHandler.RouteAsync", write.Scope); + var values = Assert.IsType(write.State); + Assert.Equal("MvcRouteHandler.RouteAsync", values.Name); + Assert.Equal(false, values.ActionSelected); + Assert.Equal(false, values.ActionInvoked); + Assert.Equal(false, values.Handled); + } + + [Fact] + public async void RouteAsync_FailOnNoInvoker_LogsCorrectValues() + { + // Arrange + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink); + + var mockInvokerFactory = new Mock(); + mockInvokerFactory.Setup(f => f.CreateInvoker(It.IsAny())) + .Returns(null); + + var context = CreateRouteContext( + invokerFactory: mockInvokerFactory.Object, + loggerFactory: loggerFactory); + + var handler = new MvcRouteHandler(); + + // Act + await Assert.ThrowsAsync(async () => + await handler.RouteAsync(context)); + + // Assert + Assert.Equal(1, sink.Scopes.Count); + var scope = sink.Scopes[0]; + Assert.Equal(typeof(MvcRouteHandler).FullName, scope.LoggerName); + Assert.Equal("MvcRouteHandler.RouteAsync", scope.Scope); + + // There is a record for IsEnabled and one for WriteCore. + Assert.Equal(2, sink.Writes.Count); + + var enabled = sink.Writes[0]; + Assert.Equal(typeof(MvcRouteHandler).FullName, enabled.LoggerName); + Assert.Equal("MvcRouteHandler.RouteAsync", enabled.Scope); + Assert.Null(enabled.State); + + var write = sink.Writes[1]; + Assert.Equal(typeof(MvcRouteHandler).FullName, write.LoggerName); + Assert.Equal("MvcRouteHandler.RouteAsync", write.Scope); + var values = Assert.IsType(write.State); + Assert.Equal("MvcRouteHandler.RouteAsync", values.Name); + Assert.Equal(true, values.ActionSelected); + Assert.Equal(false, values.ActionInvoked); + Assert.Equal(false, values.Handled); + } + + private RouteContext CreateRouteContext( + IActionSelector actionSelector = null, + IActionInvokerFactory invokerFactory = null, + ILoggerFactory loggerFactory = null) + { + var mockContextAccessor = new Mock>(); + mockContextAccessor.Setup(c => c.SetContextSource( + It.IsAny>(), + It.IsAny>())) + .Returns(NullDisposable.Instance); + + + if (actionSelector == null) + { + var mockAction = new Mock(); + + var mockActionSelector = new Mock(); + mockActionSelector.Setup(a => a.SelectAsync(It.IsAny())) + .Returns(Task.FromResult(mockAction.Object)); + + actionSelector = mockActionSelector.Object; + } + + if (invokerFactory == null) + { + var mockInvoker = new Mock(); + mockInvoker.Setup(i => i.InvokeActionAsync()) + .Returns(Task.FromResult(null)); + + var mockInvokerFactory = new Mock(); + mockInvokerFactory.Setup(f => f.CreateInvoker(It.IsAny())) + .Returns(mockInvoker.Object); + + invokerFactory = mockInvokerFactory.Object; + } + + if (loggerFactory == null) + { + loggerFactory = NullLoggerFactory.Instance; + } + + var httpContext = new Mock(); + httpContext.Setup(h => h.RequestServices.GetService(typeof(IContextAccessor))) + .Returns(mockContextAccessor.Object); + httpContext.Setup(h => h.RequestServices.GetService(typeof(IActionSelector))) + .Returns(actionSelector); + httpContext.Setup(h => h.RequestServices.GetService(typeof(IActionInvokerFactory))) + .Returns(invokerFactory); + httpContext.Setup(h => h.RequestServices.GetService(typeof(ILoggerFactory))) + .Returns(loggerFactory); + + return new RouteContext(httpContext.Object); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRouteTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRouteTests.cs index 2d28c29731..19afc71eea 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRouteTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Routing/AttributeRouteTests.cs @@ -8,7 +8,9 @@ using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Template; +using Microsoft.AspNet.Mvc.Logging; using Microsoft.Framework.OptionsModel; +using Microsoft.Framework.Logging; using Moq; using Xunit; @@ -16,6 +18,80 @@ namespace Microsoft.AspNet.Mvc.Routing { public class AttributeRouteTests { + [Fact] + public async void AttributeRoute_RouteAsyncHandled_LogsCorrectValues() + { + // Arrange + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink); + + var entry = CreateMatchingEntry("api/Store"); + var route = CreateRoutingAttributeRoute(loggerFactory, entry); + + var context = CreateRouteContext("/api/Store"); + + // Act + await route.RouteAsync(context); + + // Assert + Assert.Equal(1, sink.Scopes.Count); + var scope = sink.Scopes[0]; + Assert.Equal(typeof(AttributeRoute).FullName, scope.LoggerName); + Assert.Equal("AttributeRoute.RouteAsync", scope.Scope); + + // There is a record for IsEnabled and one for WriteCore. + Assert.Equal(2, sink.Writes.Count); + + var enabled = sink.Writes[0]; + Assert.Equal(typeof(AttributeRoute).FullName, enabled.LoggerName); + Assert.Equal("AttributeRoute.RouteAsync", enabled.Scope); + Assert.Null(enabled.State); + + var write = sink.Writes[1]; + Assert.Equal(typeof(AttributeRoute).FullName, write.LoggerName); + Assert.Equal("AttributeRoute.RouteAsync", write.Scope); + var values = Assert.IsType(write.State); + Assert.Equal("AttributeRoute.RouteAsync", values.Name); + Assert.Equal(true, values.Handled); + } + + [Fact] + public async void AttributeRoute_RouteAsyncNotHandled_LogsCorrectValues() + { + // Arrange + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink); + + var entry = CreateMatchingEntry("api/Store"); + var route = CreateRoutingAttributeRoute(loggerFactory, entry); + + var context = CreateRouteContext("/"); + + // Act + await route.RouteAsync(context); + + // Assert + Assert.Equal(1, sink.Scopes.Count); + var scope = sink.Scopes[0]; + Assert.Equal(typeof(AttributeRoute).FullName, scope.LoggerName); + Assert.Equal("AttributeRoute.RouteAsync", scope.Scope); + + // There is a record for IsEnabled and one for WriteCore. + Assert.Equal(2, sink.Writes.Count); + + var enabled = sink.Writes[0]; + Assert.Equal(typeof(AttributeRoute).FullName, enabled.LoggerName); + Assert.Equal("AttributeRoute.RouteAsync", enabled.Scope); + Assert.Null(enabled.State); + + var write = sink.Writes[1]; + Assert.Equal(typeof(AttributeRoute).FullName, write.LoggerName); + Assert.Equal("AttributeRoute.RouteAsync", write.Scope); + var values = Assert.IsType(write.State); + Assert.Equal("AttributeRoute.RouteAsync", values.Name); + Assert.Equal(false, values.Handled); + } + [Fact] public void AttributeRoute_GenerateLink_NoRequiredValues() { @@ -392,12 +468,28 @@ namespace Microsoft.AspNet.Mvc.Routing Assert.Equal("Store", path); } + private static RouteContext CreateRouteContext(string requestPath) + { + var request = new Mock(MockBehavior.Strict); + request.SetupGet(r => r.Path).Returns(new PathString(requestPath)); + + var context = new Mock(MockBehavior.Strict); + context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory))) + .Returns(NullLoggerFactory.Instance); + + context.SetupGet(c => c.Request).Returns(request.Object); + + return new RouteContext(context.Object); + } + private static VirtualPathContext CreateVirtualPathContext(object values, object ambientValues = null) { - var httpContext = Mock.Of(); + var mockHttpContext = new Mock(); + mockHttpContext.Setup(h => h.RequestServices.GetService(typeof(ILoggerFactory))) + .Returns(NullLoggerFactory.Instance); return new VirtualPathContext( - httpContext, + mockHttpContext.Object, new RouteValueDictionary(ambientValues), new RouteValueDictionary(values)); } @@ -427,6 +519,30 @@ namespace Microsoft.AspNet.Mvc.Routing return entry; } + private AttributeRouteMatchingEntry CreateMatchingEntry(string template) + { + var mockConstraint = new Mock(); + mockConstraint.Setup(c => c.Match( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny>(), + It.IsAny())) + .Returns(true); + + var mockConstraintResolver = new Mock(); + mockConstraintResolver.Setup(r => r.ResolveConstraint( + It.IsAny())) + .Returns(mockConstraint.Object); + + var entry = new AttributeRouteMatchingEntry() + { + Route = new TemplateRoute(new StubRouter(), template, mockConstraintResolver.Object) + }; + + return entry; + } + private static DefaultInlineConstraintResolver CreateConstraintResolver() { var services = Mock.Of(); @@ -458,7 +574,19 @@ namespace Microsoft.AspNet.Mvc.Routing return new AttributeRoute( next, Enumerable.Empty(), - entries); + entries, + NullLoggerFactory.Instance); + } + + private static AttributeRoute CreateRoutingAttributeRoute(ILoggerFactory loggerFactory = null, params AttributeRouteMatchingEntry[] entries) + { + loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; + + return new AttributeRoute( + new StubRouter(), + entries, + Enumerable.Empty(), + loggerFactory); } private class StubRouter : IRouter diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/StaticActionDiscoveryConventions.cs b/test/Microsoft.AspNet.Mvc.Core.Test/StaticActionDiscoveryConventions.cs index 4d173daefc..9a1e8c2e31 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/StaticActionDiscoveryConventions.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/StaticActionDiscoveryConventions.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -namespace Microsoft.AspNet.Mvc.Test +namespace Microsoft.AspNet.Mvc { /// /// An implementation of DefaultActionDiscoveryConventions that only allows controllers diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/StaticControllerAssemblyProvider.cs b/test/Microsoft.AspNet.Mvc.Core.Test/StaticControllerAssemblyProvider.cs index f76815d4ed..1a00b99bec 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/StaticControllerAssemblyProvider.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/StaticControllerAssemblyProvider.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Reflection; -namespace Microsoft.AspNet.Mvc.Test +namespace Microsoft.AspNet.Mvc { /// /// An implementation of IControllerAssemblyProvider that provides just this assembly. diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs index ca94626f6e..74d936ae83 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs @@ -9,6 +9,7 @@ using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.OptionsModel; using Moq; using Xunit; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc.Core.Test { @@ -445,8 +446,13 @@ namespace Microsoft.AspNet.Mvc.Core.Test Assert.Equal("/app/named/home2/newaction/someid", url); } - private static HttpContext CreateHttpContext(string appRoot) + private static HttpContext CreateHttpContext(string appRoot, ILoggerFactory factory = null) { + if(factory == null) + { + factory = NullLoggerFactory.Instance; + } + var appRootPath = new PathString(appRoot); var request = new Mock(); request.SetupGet(r => r.PathBase) @@ -454,6 +460,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test request.SetupGet(r => r.Host) .Returns(new HostString("localhost")); var context = new Mock(); + context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory))) + .Returns(factory); context.SetupGet(c => c.Request) .Returns(request.Object); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/project.json b/test/Microsoft.AspNet.Mvc.Core.Test/project.json index 36d78ccc11..28f4d47e51 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.Core.Test/project.json @@ -12,6 +12,7 @@ "Microsoft.AspNet.Routing": "1.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", "Microsoft.Framework.DependencyInjection": "1.0.0-*", + "Microsoft.Framework.Logging": "1.0.0-*", "Moq": "4.2.1312.1622", "Xunit.KRunner": "1.0.0-*" }, diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Microsoft.AspNet.Mvc.FunctionalTests.kproj b/test/Microsoft.AspNet.Mvc.FunctionalTests/Microsoft.AspNet.Mvc.FunctionalTests.kproj index 342dae7c28..c60a012307 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/Microsoft.AspNet.Mvc.FunctionalTests.kproj +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Microsoft.AspNet.Mvc.FunctionalTests.kproj @@ -30,21 +30,22 @@ + + + + + - - - - \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/NullLoggerFactory.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/NullLoggerFactory.cs new file mode 100644 index 0000000000..56f9996fbc --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/NullLoggerFactory.cs @@ -0,0 +1,40 @@ +using Microsoft.Framework.Logging; +using System; + +namespace Microsoft.AspNet.Mvc +{ + public class NullLoggerFactory : ILoggerFactory + { + public static NullLoggerFactory Instance = new NullLoggerFactory(); + + public ILogger Create(string name) + { + return NullLogger.Instance; + } + } + + public class NullLogger : ILogger + { + public static NullLogger Instance = new NullLogger(); + + public IDisposable BeginScope(object state) + { + return NullDisposable.Instance; + } + + public bool WriteCore(TraceType eventType, int eventId, object state, Exception exception, Func formatter) + { + return false; + } + } + + public class NullDisposable : IDisposable + { + public static NullDisposable Instance = new NullDisposable(); + + public void Dispose() + { + // intentionally does nothing + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/TestHelper.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/TestHelper.cs index caa0dc09c7..4d719252c7 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/TestHelper.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/TestHelper.cs @@ -15,6 +15,7 @@ using Microsoft.Framework.Runtime; using Microsoft.Framework.Runtime.Infrastructure; using Xunit; using Microsoft.Framework.ConfigurationModel; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc.FunctionalTests { @@ -51,6 +52,10 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests typeof(ITestConfigurationProvider), configuration); + services.AddInstance( + typeof(ILoggerFactory), + NullLoggerFactory.Instance); + return services.BuildServiceProvider(originalProvider); } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json index 0a4fcdc129..e9a88b59dd 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json @@ -14,6 +14,7 @@ "Microsoft.Framework.ConfigurationModel": "1.0.0-*", "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*", "Microsoft.Framework.DependencyInjection": "1.0.0-*", + "Microsoft.Framework.Logging": "1.0.0-*", "Microsoft.Framework.Runtime.Interfaces": "1.0.0-*", "Microsoft.AspNet.PipelineCore": "1.0.0-*", "RoutingWebSite": "", diff --git a/test/WebSites/BasicWebSite/BasicWebSite.kproj b/test/WebSites/BasicWebSite/BasicWebSite.kproj index 595e02ecc3..c2039cb40b 100644 --- a/test/WebSites/BasicWebSite/BasicWebSite.kproj +++ b/test/WebSites/BasicWebSite/BasicWebSite.kproj @@ -29,9 +29,9 @@ - +