From 274c9dbc4bf621f2246cd73f07d13723442fc589 Mon Sep 17 00:00:00 2001 From: Suhas Joshi Date: Mon, 8 Dec 2014 15:24:52 -0800 Subject: [PATCH 001/118] Updating to dev NuGet.config --- NuGet.Config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGet.Config b/NuGet.Config index 2d3b0cb857..f41e9c631d 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,7 +1,7 @@  - + From 66eb3af8060c9dc067ec1e34c3aee50209ad20bc Mon Sep 17 00:00:00 2001 From: SonjaKhan Date: Tue, 9 Dec 2014 10:55:22 -0800 Subject: [PATCH 002/118] random cleanup --- samples/MvcSample.Web/project.json | 2 +- .../ActionConstraints/IActionConstraint.cs | 2 +- src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs | 2 +- src/Microsoft.AspNet.Mvc.Razor/IViewLocationExpander.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samples/MvcSample.Web/project.json b/samples/MvcSample.Web/project.json index 1071fb93ee..a2c73e813f 100644 --- a/samples/MvcSample.Web/project.json +++ b/samples/MvcSample.Web/project.json @@ -8,7 +8,7 @@ "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*", "Microsoft.AspNet.Server.IIS": "1.0.0-*", "Microsoft.AspNet.Server.WebListener": "1.0.0-*", - "Microsoft.AspNet.StaticFiles": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "commands": { "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001", diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionConstraints/IActionConstraint.cs b/src/Microsoft.AspNet.Mvc.Core/ActionConstraints/IActionConstraint.cs index 3fa0638ed6..c3a2eea903 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionConstraints/IActionConstraint.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionConstraints/IActionConstraint.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc /// are candidates for selection, the next stage to run is the lowest value of for any /// constraint of any candidate which is greater than the order of the last stage. /// - /// Once the stage order is identified, each action has all of it's constraints in that stage executed. + /// Once the stage order is identified, each action has all of its constraints in that stage executed. /// If any constraint does not match, then that action is not a candidate for selection. If any actions /// with constraints in the current state are still candidates, then those are the 'best' actions and this /// process will repeat with the next stage on the set of 'best' actions. If after processing the diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs index f4e951f12e..45454630f8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Mvc public class ViewResult : ActionResult { /// - /// /// Gets or sets the name of the view to render. + /// Gets or sets the name of the view to render. /// /// /// When null, defaults to . diff --git a/src/Microsoft.AspNet.Mvc.Razor/IViewLocationExpander.cs b/src/Microsoft.AspNet.Mvc.Razor/IViewLocationExpander.cs index 8fa598326d..73c089dd35 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/IViewLocationExpander.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/IViewLocationExpander.cs @@ -37,7 +37,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// The for the current view location /// expansion operation. - /// The sequence of view locations to expand. + /// The sequence of view locations to expand. /// A list of expanded view locations. IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable viewLocations); From e65218d6dfe61424e53767b900af4c15afa07298 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 10 Dec 2014 13:23:42 -0800 Subject: [PATCH 003/118] Adding servers and wwwroot folder to test websites Adding dependencies and commands for iis, web listener and khestrel to each site. Each website comes with a readme.md to 'anchor' the otherwise empty folder. We have another work item tracking adding content to these. Once VS sees a project with a wwwroot, it wants to assign a port for iis, so I let it. --- samples/MvcSample.Web/project.json | 9 +++-- samples/TagHelperSample.Web/project.json | 38 ++++++++++-------- samples/TagHelperSample.Web/wwwroot/readme.md | Bin 0 -> 84 bytes .../ActivatorWebSite/ActivatorWebSite.kproj | 5 ++- test/WebSites/ActivatorWebSite/project.json | 12 +++++- .../ActivatorWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../AddServicesWebSite.kproj | 5 ++- test/WebSites/AddServicesWebSite/project.json | 13 +++++- .../AddServicesWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../AntiForgeryWebSite.kproj | 5 ++- test/WebSites/AntiForgeryWebSite/project.json | 12 ++++-- .../AntiForgeryWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../ApiExplorerWebSite.kproj | 5 ++- test/WebSites/ApiExplorerWebSite/project.json | 12 +++++- .../ApiExplorerWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../ApplicationModelWebSite.kproj | 5 ++- .../ApplicationModelWebSite/project.json | 13 +++++- .../ApplicationModelWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../AutofacWebSite/AutofacWebSite.kproj | 5 ++- test/WebSites/AutofacWebSite/project.json | 16 ++++++-- .../WebSites/AutofacWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes test/WebSites/BasicWebSite/BasicWebSite.kproj | 5 ++- test/WebSites/BasicWebSite/project.json | 12 +++++- test/WebSites/BasicWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../CompositeViewEngine.kproj | 5 ++- .../WebSites/CompositeViewEngine/project.json | 12 +++++- .../CompositeViewEngine/wwwroot/readme.md | Bin 0 -> 30 bytes .../ConnegWebSite/ConnegWebsite.kproj | 5 ++- test/WebSites/ConnegWebSite/project.json | 12 +++++- test/WebSites/ConnegWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes test/WebSites/FilesWebSite/project.json | 11 ++++- test/WebSites/FilesWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../FiltersWebSite/FiltersWebSite.kproj | 5 ++- test/WebSites/FiltersWebSite/project.json | 20 ++++++--- .../WebSites/FiltersWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../FormatterWebSite/FormatterWebSite.kproj | 5 ++- test/WebSites/FormatterWebSite/project.json | 16 ++++++-- .../FormatterWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../InlineConstraintsWebSite/project.json | 13 ++++-- .../wwwroot/readme.md | Bin 0 -> 30 bytes .../project.json | 8 ++-- .../ModelBindingWebSite.kproj | 5 ++- .../WebSites/ModelBindingWebSite/project.json | 13 +++++- .../ModelBindingWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../PrecompilationWebSite/project.json | 10 ++++- .../PrecompilationWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../RazorInstrumentationWebsite.kproj | 5 ++- .../RazorInstrumentationWebsite/project.json | 12 +++++- .../wwwroot/readme.md | Bin 0 -> 30 bytes test/WebSites/RazorWebSite/RazorWebSite.kproj | 5 ++- test/WebSites/RazorWebSite/project.json | 12 +++++- test/WebSites/RazorWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../RequestServicesWebSite/project.json | 15 +++++-- .../RequestServicesWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../RoutingWebSite/RoutingWebSite.kproj | 5 ++- test/WebSites/RoutingWebSite/project.json | 12 +++++- .../WebSites/RoutingWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../TagHelpersWebSite/TagHelpersWebSite.kproj | 5 ++- test/WebSites/TagHelpersWebSite/project.json | 27 ++++++++----- .../TagHelpersWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../UrlHelperWebSite/UrlHelperWebSite.kproj | 5 ++- test/WebSites/UrlHelperWebSite/project.json | 14 +++++-- .../UrlHelperWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../ValueProvidersSite.kproj | 5 ++- test/WebSites/ValueProvidersSite/project.json | 12 +++++- .../ValueProvidersSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../VersioningWebSite/VersioningWebSite.kproj | 5 ++- test/WebSites/VersioningWebSite/project.json | 12 +++++- .../VersioningWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../ViewComponentWebSite/project.json | 10 ++++- .../ViewComponentWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../WebApiCompatShimWebSite/project.json | 9 ++++- .../WebApiCompatShimWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes .../XmlSerializerWebSite.kproj | 5 ++- .../XmlSerializerWebSite/project.json | 13 +++++- .../XmlSerializerWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes 76 files changed, 364 insertions(+), 136 deletions(-) create mode 100644 samples/TagHelperSample.Web/wwwroot/readme.md create mode 100644 test/WebSites/ActivatorWebSite/wwwroot/readme.md create mode 100644 test/WebSites/AddServicesWebSite/wwwroot/readme.md create mode 100644 test/WebSites/AntiForgeryWebSite/wwwroot/readme.md create mode 100644 test/WebSites/ApiExplorerWebSite/wwwroot/readme.md create mode 100644 test/WebSites/ApplicationModelWebSite/wwwroot/readme.md create mode 100644 test/WebSites/AutofacWebSite/wwwroot/readme.md create mode 100644 test/WebSites/BasicWebSite/wwwroot/readme.md create mode 100644 test/WebSites/CompositeViewEngine/wwwroot/readme.md create mode 100644 test/WebSites/ConnegWebSite/wwwroot/readme.md create mode 100644 test/WebSites/FilesWebSite/wwwroot/readme.md create mode 100644 test/WebSites/FiltersWebSite/wwwroot/readme.md create mode 100644 test/WebSites/FormatterWebSite/wwwroot/readme.md create mode 100644 test/WebSites/InlineConstraintsWebSite/wwwroot/readme.md create mode 100644 test/WebSites/ModelBindingWebSite/wwwroot/readme.md create mode 100644 test/WebSites/PrecompilationWebSite/wwwroot/readme.md create mode 100644 test/WebSites/RazorInstrumentationWebsite/wwwroot/readme.md create mode 100644 test/WebSites/RazorWebSite/wwwroot/readme.md create mode 100644 test/WebSites/RequestServicesWebSite/wwwroot/readme.md create mode 100644 test/WebSites/RoutingWebSite/wwwroot/readme.md create mode 100644 test/WebSites/TagHelpersWebSite/wwwroot/readme.md create mode 100644 test/WebSites/UrlHelperWebSite/wwwroot/readme.md create mode 100644 test/WebSites/ValueProvidersSite/wwwroot/readme.md create mode 100644 test/WebSites/VersioningWebSite/wwwroot/readme.md create mode 100644 test/WebSites/ViewComponentWebSite/wwwroot/readme.md create mode 100644 test/WebSites/WebApiCompatShimWebSite/wwwroot/readme.md create mode 100644 test/WebSites/XmlSerializerWebSite/wwwroot/readme.md diff --git a/samples/MvcSample.Web/project.json b/samples/MvcSample.Web/project.json index a2c73e813f..fd599bee10 100644 --- a/samples/MvcSample.Web/project.json +++ b/samples/MvcSample.Web/project.json @@ -1,19 +1,20 @@ { + "commands": { + "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001", + "kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000" + }, "compilationOptions": { "warningsAsErrors": true }, "dependencies": { "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*", "Microsoft.AspNet.Server.IIS": "1.0.0-*", "Microsoft.AspNet.Server.WebListener": "1.0.0-*", "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, - "commands": { - "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001", - "kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000" - }, "frameworks": { "aspnet50": { "dependencies": { diff --git a/samples/TagHelperSample.Web/project.json b/samples/TagHelperSample.Web/project.json index dcdff7a4d3..47b6449544 100644 --- a/samples/TagHelperSample.Web/project.json +++ b/samples/TagHelperSample.Web/project.json @@ -1,19 +1,23 @@ { - "webroot": ".", - "compilationOptions": { - "warningsAsErrors": true - }, - "dependencies": { - "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Server.WebListener": "1.0.0-*", - "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*" - }, - "commands": { - "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001" - }, - "frameworks": { - "aspnet50": {}, - "aspnetcore50": {} - } + "commands": { + "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001", + "kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000" + }, + "compilationOptions": { + "warningsAsErrors": true + }, + "dependencies": { + "Kestrel": "1.0.0-*", + "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" + }, + "frameworks": { + "aspnet50": { }, + "aspnetcore50": { } + }, + "webroot": "wwwroot" } diff --git a/samples/TagHelperSample.Web/wwwroot/readme.md b/samples/TagHelperSample.Web/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..140de4b8b475dbba253342a1048b20bfb6fd7a38 GIT binary patch literal 84 zcmXYn-3fp&7z1w<$8mrzAX5LRSo(E-^$=f@T$1NbZ0rQXuU*bkFmX~TA5|I`hk2&E QE>hEzua(ih_|0~~3l}#LA^-pY literal 0 HcmV?d00001 diff --git a/test/WebSites/ActivatorWebSite/ActivatorWebSite.kproj b/test/WebSites/ActivatorWebSite/ActivatorWebSite.kproj index f44ce7bb91..3266173414 100644 --- a/test/WebSites/ActivatorWebSite/ActivatorWebSite.kproj +++ b/test/WebSites/ActivatorWebSite/ActivatorWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49622 - + \ No newline at end of file diff --git a/test/WebSites/ActivatorWebSite/project.json b/test/WebSites/ActivatorWebSite/project.json index 1a2adbee1b..c582a1382f 100644 --- a/test/WebSites/ActivatorWebSite/project.json +++ b/test/WebSites/ActivatorWebSite/project.json @@ -1,11 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/ActivatorWebSite/wwwroot/readme.md b/test/WebSites/ActivatorWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/AddServicesWebSite/AddServicesWebSite.kproj b/test/WebSites/AddServicesWebSite/AddServicesWebSite.kproj index c1c0e6b776..f58ee7ea89 100644 --- a/test/WebSites/AddServicesWebSite/AddServicesWebSite.kproj +++ b/test/WebSites/AddServicesWebSite/AddServicesWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49640 - + \ No newline at end of file diff --git a/test/WebSites/AddServicesWebSite/project.json b/test/WebSites/AddServicesWebSite/project.json index 5e8e58f8ed..c582a1382f 100644 --- a/test/WebSites/AddServicesWebSite/project.json +++ b/test/WebSites/AddServicesWebSite/project.json @@ -1,10 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/AddServicesWebSite/wwwroot/readme.md b/test/WebSites/AddServicesWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/AntiForgeryWebSite/AntiForgeryWebSite.kproj b/test/WebSites/AntiForgeryWebSite/AntiForgeryWebSite.kproj index ffc5de0eda..027df5950f 100644 --- a/test/WebSites/AntiForgeryWebSite/AntiForgeryWebSite.kproj +++ b/test/WebSites/AntiForgeryWebSite/AntiForgeryWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49637 - + \ No newline at end of file diff --git a/test/WebSites/AntiForgeryWebSite/project.json b/test/WebSites/AntiForgeryWebSite/project.json index 2827c73ae2..c582a1382f 100644 --- a/test/WebSites/AntiForgeryWebSite/project.json +++ b/test/WebSites/AntiForgeryWebSite/project.json @@ -1,13 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Mvc.Razor": "6.0.0-*", - "Microsoft.AspNet.Security.Cookies": "1.0.0-*" + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/AntiForgeryWebSite/wwwroot/readme.md b/test/WebSites/AntiForgeryWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerWebSite.kproj b/test/WebSites/ApiExplorerWebSite/ApiExplorerWebSite.kproj index e6e72e1c6d..1c8d4bce31 100644 --- a/test/WebSites/ApiExplorerWebSite/ApiExplorerWebSite.kproj +++ b/test/WebSites/ApiExplorerWebSite/ApiExplorerWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49644 - + \ No newline at end of file diff --git a/test/WebSites/ApiExplorerWebSite/project.json b/test/WebSites/ApiExplorerWebSite/project.json index cd59156d97..6fbc582927 100644 --- a/test/WebSites/ApiExplorerWebSite/project.json +++ b/test/WebSites/ApiExplorerWebSite/project.json @@ -1,11 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } \ No newline at end of file diff --git a/test/WebSites/ApiExplorerWebSite/wwwroot/readme.md b/test/WebSites/ApiExplorerWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.kproj b/test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.kproj index 7da8d7eeff..c8a95d63d4 100644 --- a/test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.kproj +++ b/test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49648 - + \ No newline at end of file diff --git a/test/WebSites/ApplicationModelWebSite/project.json b/test/WebSites/ApplicationModelWebSite/project.json index 5e8e58f8ed..c582a1382f 100644 --- a/test/WebSites/ApplicationModelWebSite/project.json +++ b/test/WebSites/ApplicationModelWebSite/project.json @@ -1,10 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/ApplicationModelWebSite/wwwroot/readme.md b/test/WebSites/ApplicationModelWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/AutofacWebSite/AutofacWebSite.kproj b/test/WebSites/AutofacWebSite/AutofacWebSite.kproj index 54a6a3600d..83a80820c7 100644 --- a/test/WebSites/AutofacWebSite/AutofacWebSite.kproj +++ b/test/WebSites/AutofacWebSite/AutofacWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49623 - + \ No newline at end of file diff --git a/test/WebSites/AutofacWebSite/project.json b/test/WebSites/AutofacWebSite/project.json index ed5e88526e..5dca4f019b 100644 --- a/test/WebSites/AutofacWebSite/project.json +++ b/test/WebSites/AutofacWebSite/project.json @@ -1,12 +1,20 @@ { + "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" + }, "dependencies": { - "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Autofac": "3.3.0", + "Kestrel": "1.0.0-*", + "Microsoft.Framework.DependencyInjection.Autofac": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", - "Microsoft.Framework.DependencyInjection.Autofac": "1.0.0-*", - "Autofac": "3.3.0" + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/AutofacWebSite/wwwroot/readme.md b/test/WebSites/AutofacWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/BasicWebSite/BasicWebSite.kproj b/test/WebSites/BasicWebSite/BasicWebSite.kproj index 6c34744509..6a9caf2490 100644 --- a/test/WebSites/BasicWebSite/BasicWebSite.kproj +++ b/test/WebSites/BasicWebSite/BasicWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49621 - + \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/project.json b/test/WebSites/BasicWebSite/project.json index 1a2adbee1b..c582a1382f 100644 --- a/test/WebSites/BasicWebSite/project.json +++ b/test/WebSites/BasicWebSite/project.json @@ -1,11 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/BasicWebSite/wwwroot/readme.md b/test/WebSites/BasicWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/CompositeViewEngine/CompositeViewEngine.kproj b/test/WebSites/CompositeViewEngine/CompositeViewEngine.kproj index 987d357f17..21745d6cc8 100644 --- a/test/WebSites/CompositeViewEngine/CompositeViewEngine.kproj +++ b/test/WebSites/CompositeViewEngine/CompositeViewEngine.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 8643 - + \ No newline at end of file diff --git a/test/WebSites/CompositeViewEngine/project.json b/test/WebSites/CompositeViewEngine/project.json index 1a2adbee1b..c582a1382f 100644 --- a/test/WebSites/CompositeViewEngine/project.json +++ b/test/WebSites/CompositeViewEngine/project.json @@ -1,11 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/CompositeViewEngine/wwwroot/readme.md b/test/WebSites/CompositeViewEngine/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/ConnegWebSite/ConnegWebsite.kproj b/test/WebSites/ConnegWebSite/ConnegWebsite.kproj index 55b94c4677..0fdbdc7738 100644 --- a/test/WebSites/ConnegWebSite/ConnegWebsite.kproj +++ b/test/WebSites/ConnegWebSite/ConnegWebsite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49636 - + \ No newline at end of file diff --git a/test/WebSites/ConnegWebSite/project.json b/test/WebSites/ConnegWebSite/project.json index 1a2adbee1b..c582a1382f 100644 --- a/test/WebSites/ConnegWebSite/project.json +++ b/test/WebSites/ConnegWebSite/project.json @@ -1,11 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/ConnegWebSite/wwwroot/readme.md b/test/WebSites/ConnegWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/FilesWebSite/project.json b/test/WebSites/FilesWebSite/project.json index b878714191..8e0ef4a67b 100644 --- a/test/WebSites/FilesWebSite/project.json +++ b/test/WebSites/FilesWebSite/project.json @@ -1,12 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.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.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } }, - "webroot" : "." + "webroot": "." } diff --git a/test/WebSites/FilesWebSite/wwwroot/readme.md b/test/WebSites/FilesWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/FiltersWebSite/FiltersWebSite.kproj b/test/WebSites/FiltersWebSite/FiltersWebSite.kproj index 1918d4d523..b979cc3d87 100644 --- a/test/WebSites/FiltersWebSite/FiltersWebSite.kproj +++ b/test/WebSites/FiltersWebSite/FiltersWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49641 - + \ No newline at end of file diff --git a/test/WebSites/FiltersWebSite/project.json b/test/WebSites/FiltersWebSite/project.json index b3d8d337ba..c582a1382f 100644 --- a/test/WebSites/FiltersWebSite/project.json +++ b/test/WebSites/FiltersWebSite/project.json @@ -1,11 +1,19 @@ { - "dependencies": { - "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "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" : { + "dependencies": { + "Kestrel": "1.0.0-*", + "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" + }, + "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/FiltersWebSite/wwwroot/readme.md b/test/WebSites/FiltersWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/FormatterWebSite/FormatterWebSite.kproj b/test/WebSites/FormatterWebSite/FormatterWebSite.kproj index ecf0288aba..5ac17019bb 100644 --- a/test/WebSites/FormatterWebSite/FormatterWebSite.kproj +++ b/test/WebSites/FormatterWebSite/FormatterWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49634 - + \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/project.json b/test/WebSites/FormatterWebSite/project.json index 379acc1190..88344f5c32 100644 --- a/test/WebSites/FormatterWebSite/project.json +++ b/test/WebSites/FormatterWebSite/project.json @@ -1,16 +1,24 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { - "aspnet50": { - }, + "aspnet50": { }, "aspnetcore50": { "dependencies": { "System.Runtime.Serialization.Xml": "4.0.0-beta-*", "System.Xml.ReaderWriter": "4.0.10-beta-*" } } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/FormatterWebSite/wwwroot/readme.md b/test/WebSites/FormatterWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/InlineConstraintsWebSite/project.json b/test/WebSites/InlineConstraintsWebSite/project.json index 5a3dc9f49b..c582a1382f 100644 --- a/test/WebSites/InlineConstraintsWebSite/project.json +++ b/test/WebSites/InlineConstraintsWebSite/project.json @@ -1,12 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Server.IIS": "1.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", - "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*" + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/InlineConstraintsWebSite/wwwroot/readme.md b/test/WebSites/InlineConstraintsWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/project.json b/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/project.json index 9548933b64..baf60416ff 100644 --- a/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/project.json +++ b/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/project.json @@ -1,11 +1,11 @@ { "dependencies": { - "Microsoft.AspNet.Http": "1.0.0-*", + "Microsoft.AspNet.Http": "1.0.0-*", "Microsoft.Framework.ConfigurationModel": "1.0.0-*", "Microsoft.Framework.DependencyInjection": "1.0.0-*" }, - "frameworks" : { - "aspnet50" : { }, - "aspnetcore50" : { } + "frameworks": { + "aspnet50": { }, + "aspnetcore50": { } } } diff --git a/test/WebSites/ModelBindingWebSite/ModelBindingWebSite.kproj b/test/WebSites/ModelBindingWebSite/ModelBindingWebSite.kproj index fb9a92a165..9c46d8d2c1 100644 --- a/test/WebSites/ModelBindingWebSite/ModelBindingWebSite.kproj +++ b/test/WebSites/ModelBindingWebSite/ModelBindingWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49635 - + \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/project.json b/test/WebSites/ModelBindingWebSite/project.json index 5e8e58f8ed..c582a1382f 100644 --- a/test/WebSites/ModelBindingWebSite/project.json +++ b/test/WebSites/ModelBindingWebSite/project.json @@ -1,10 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/ModelBindingWebSite/wwwroot/readme.md b/test/WebSites/ModelBindingWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/PrecompilationWebSite/project.json b/test/WebSites/PrecompilationWebSite/project.json index 74663e7fc8..01696e38f0 100644 --- a/test/WebSites/PrecompilationWebSite/project.json +++ b/test/WebSites/PrecompilationWebSite/project.json @@ -1,13 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "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-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } }, - "webroot": "." + "webroot": "wwwroot" } diff --git a/test/WebSites/PrecompilationWebSite/wwwroot/readme.md b/test/WebSites/PrecompilationWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/RazorInstrumentationWebsite/RazorInstrumentationWebsite.kproj b/test/WebSites/RazorInstrumentationWebsite/RazorInstrumentationWebsite.kproj index f30fed371f..e10fb5f36f 100644 --- a/test/WebSites/RazorInstrumentationWebsite/RazorInstrumentationWebsite.kproj +++ b/test/WebSites/RazorInstrumentationWebsite/RazorInstrumentationWebsite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49647 - + \ No newline at end of file diff --git a/test/WebSites/RazorInstrumentationWebsite/project.json b/test/WebSites/RazorInstrumentationWebsite/project.json index 1a2adbee1b..c582a1382f 100644 --- a/test/WebSites/RazorInstrumentationWebsite/project.json +++ b/test/WebSites/RazorInstrumentationWebsite/project.json @@ -1,11 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/RazorInstrumentationWebsite/wwwroot/readme.md b/test/WebSites/RazorInstrumentationWebsite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/RazorWebSite/RazorWebSite.kproj b/test/WebSites/RazorWebSite/RazorWebSite.kproj index f7d0562f47..240d31008d 100644 --- a/test/WebSites/RazorWebSite/RazorWebSite.kproj +++ b/test/WebSites/RazorWebSite/RazorWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49633 - + \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/project.json b/test/WebSites/RazorWebSite/project.json index 1a2adbee1b..c582a1382f 100644 --- a/test/WebSites/RazorWebSite/project.json +++ b/test/WebSites/RazorWebSite/project.json @@ -1,11 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/RazorWebSite/wwwroot/readme.md b/test/WebSites/RazorWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/RequestServicesWebSite/project.json b/test/WebSites/RequestServicesWebSite/project.json index 072410a212..f13fd03388 100644 --- a/test/WebSites/RequestServicesWebSite/project.json +++ b/test/WebSites/RequestServicesWebSite/project.json @@ -1,13 +1,20 @@ { "webroot": "wwwroot", "exclude": "wwwroot/**/*", + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.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.StaticFiles": "1.0.0-*" }, - "frameworks" : { - "aspnet50" : { }, - "aspnetcore50" : { } + "frameworks": { + "aspnet50": { }, + "aspnetcore50": { } } } diff --git a/test/WebSites/RequestServicesWebSite/wwwroot/readme.md b/test/WebSites/RequestServicesWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/RoutingWebSite/RoutingWebSite.kproj b/test/WebSites/RoutingWebSite/RoutingWebSite.kproj index e695a70a1f..eef5ca37f6 100644 --- a/test/WebSites/RoutingWebSite/RoutingWebSite.kproj +++ b/test/WebSites/RoutingWebSite/RoutingWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49632 - + \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/project.json b/test/WebSites/RoutingWebSite/project.json index f1e1deac1f..c582a1382f 100644 --- a/test/WebSites/RoutingWebSite/project.json +++ b/test/WebSites/RoutingWebSite/project.json @@ -1,11 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.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.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/RoutingWebSite/wwwroot/readme.md b/test/WebSites/RoutingWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/TagHelpersWebSite/TagHelpersWebSite.kproj b/test/WebSites/TagHelpersWebSite/TagHelpersWebSite.kproj index 7dcc635bca..7ec36fbf49 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpersWebSite.kproj +++ b/test/WebSites/TagHelpersWebSite/TagHelpersWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49646 - + \ No newline at end of file diff --git a/test/WebSites/TagHelpersWebSite/project.json b/test/WebSites/TagHelpersWebSite/project.json index 538c221b5c..ce5714d371 100644 --- a/test/WebSites/TagHelpersWebSite/project.json +++ b/test/WebSites/TagHelpersWebSite/project.json @@ -1,11 +1,20 @@ { - "dependencies": { - "Microsoft.AspNet.Mvc": "", - "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "" - }, - "frameworks": { - "aspnet50": {}, - "aspnetcore50": {} - } + "commands": { + "web": "Microsoft.AspNet.Hosting server=Microsoft.AspNet.Server.WebListener server.urls=http://localhost:5001", + "kestrel": "Microsoft.AspNet.Hosting --server Kestrel --server.urls http://localhost:5000" + }, + "dependencies": { + "Kestrel": "1.0.0-*", + "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" + }, + "frameworks": { + "aspnet50": { }, + "aspnetcore50": { } + }, + "webroot": "wwwroot" } \ No newline at end of file diff --git a/test/WebSites/TagHelpersWebSite/wwwroot/readme.md b/test/WebSites/TagHelpersWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/UrlHelperWebSite/UrlHelperWebSite.kproj b/test/WebSites/UrlHelperWebSite/UrlHelperWebSite.kproj index 5bde93d4e8..93028f60db 100644 --- a/test/WebSites/UrlHelperWebSite/UrlHelperWebSite.kproj +++ b/test/WebSites/UrlHelperWebSite/UrlHelperWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49643 - + \ No newline at end of file diff --git a/test/WebSites/UrlHelperWebSite/project.json b/test/WebSites/UrlHelperWebSite/project.json index 84520f9cf5..c582a1382f 100644 --- a/test/WebSites/UrlHelperWebSite/project.json +++ b/test/WebSites/UrlHelperWebSite/project.json @@ -1,13 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Server.IIS": "1.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", - "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*", - "Microsoft.Framework.OptionsModel": "1.0.0-*" + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/UrlHelperWebSite/wwwroot/readme.md b/test/WebSites/UrlHelperWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/ValueProvidersSite/ValueProvidersSite.kproj b/test/WebSites/ValueProvidersSite/ValueProvidersSite.kproj index 530a0ccc3e..2ee15f52d7 100644 --- a/test/WebSites/ValueProvidersSite/ValueProvidersSite.kproj +++ b/test/WebSites/ValueProvidersSite/ValueProvidersSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 1100 - + \ No newline at end of file diff --git a/test/WebSites/ValueProvidersSite/project.json b/test/WebSites/ValueProvidersSite/project.json index 1a2adbee1b..c582a1382f 100644 --- a/test/WebSites/ValueProvidersSite/project.json +++ b/test/WebSites/ValueProvidersSite/project.json @@ -1,11 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.AspNet.Server.IIS": "1.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/ValueProvidersSite/wwwroot/readme.md b/test/WebSites/ValueProvidersSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/VersioningWebSite/VersioningWebSite.kproj b/test/WebSites/VersioningWebSite/VersioningWebSite.kproj index 5698377df7..581db64986 100644 --- a/test/WebSites/VersioningWebSite/VersioningWebSite.kproj +++ b/test/WebSites/VersioningWebSite/VersioningWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49645 - + \ No newline at end of file diff --git a/test/WebSites/VersioningWebSite/project.json b/test/WebSites/VersioningWebSite/project.json index f1e1deac1f..c582a1382f 100644 --- a/test/WebSites/VersioningWebSite/project.json +++ b/test/WebSites/VersioningWebSite/project.json @@ -1,11 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.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.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/VersioningWebSite/wwwroot/readme.md b/test/WebSites/VersioningWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/ViewComponentWebSite/project.json b/test/WebSites/ViewComponentWebSite/project.json index 0c1b3e5ffb..c582a1382f 100644 --- a/test/WebSites/ViewComponentWebSite/project.json +++ b/test/WebSites/ViewComponentWebSite/project.json @@ -1,7 +1,15 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, diff --git a/test/WebSites/ViewComponentWebSite/wwwroot/readme.md b/test/WebSites/ViewComponentWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/WebApiCompatShimWebSite/project.json b/test/WebSites/WebApiCompatShimWebSite/project.json index 592bcef0f6..e374a3d0ec 100644 --- a/test/WebSites/WebApiCompatShimWebSite/project.json +++ b/test/WebSites/WebApiCompatShimWebSite/project.json @@ -1,9 +1,16 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.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.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, diff --git a/test/WebSites/WebApiCompatShimWebSite/wwwroot/readme.md b/test/WebSites/WebApiCompatShimWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 diff --git a/test/WebSites/XmlSerializerWebSite/XmlSerializerWebSite.kproj b/test/WebSites/XmlSerializerWebSite/XmlSerializerWebSite.kproj index a50358e98e..520ceb278b 100644 --- a/test/WebSites/XmlSerializerWebSite/XmlSerializerWebSite.kproj +++ b/test/WebSites/XmlSerializerWebSite/XmlSerializerWebSite.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -12,6 +12,7 @@ 2.0 + 49642 - + \ No newline at end of file diff --git a/test/WebSites/XmlSerializerWebSite/project.json b/test/WebSites/XmlSerializerWebSite/project.json index 5e8e58f8ed..c582a1382f 100644 --- a/test/WebSites/XmlSerializerWebSite/project.json +++ b/test/WebSites/XmlSerializerWebSite/project.json @@ -1,10 +1,19 @@ { + "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" + }, "dependencies": { + "Kestrel": "1.0.0-*", "Microsoft.AspNet.Mvc": "6.0.0-*", - "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0" + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { "aspnet50": { }, "aspnetcore50": { } - } + }, + "webroot": "wwwroot" } diff --git a/test/WebSites/XmlSerializerWebSite/wwwroot/readme.md b/test/WebSites/XmlSerializerWebSite/wwwroot/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..123252603f372fb2c5fac8e1c7e3f2bec12666a9 GIT binary patch literal 30 hcmezW&x0YAAqNQa8FUyF7{Y;c5s;U{z{|kJ004r123-IE literal 0 HcmV?d00001 From b4975b779cdd336d7f7965f074887cac7904212f Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 10 Dec 2014 14:29:05 -0800 Subject: [PATCH 004/118] Update names of WebSite projects for consistency Project/Assembly names are all like 'FeatureWebSite' root namespaces updated accordingly. This makes processing all of the functional tests and deploying the web sites much simpler. --- Mvc.sln | 58 ++++++++++-------- .../ApiExplorerTest.cs | 16 ++--- .../CompositeViewEngineTests.cs | 4 +- .../ConnegTests.cs | 4 +- .../OutputFormatterTest.cs | 4 +- .../ValueProviderTest.cs | 4 +- .../project.json | 4 +- .../ApiExplorerDataFilter.cs | 2 +- ...ApiExplorerVisibilityDisabledConvention.cs | 2 +- .../ApiExplorerVisibilityEnabledConvention.cs | 2 +- .../ApiExplorerHttpMethodController.cs | 2 +- ...piExplorerNameSetByConventionController.cs | 2 +- .../ApiExplorerNameSetExplicitlyController.cs | 2 +- ...piExplorerResponseContentTypeController.cs | 2 +- ...seContentTypeOverrideOnActionController.cs | 2 +- ...rResponseTypeOverrideOnActionController.cs | 2 +- ...orerResponseTypeWithAttributeController.cs | 2 +- ...rResponseTypeWithoutAttributeController.cs | 2 +- ...eAndPathParametersInformationController.cs | 2 +- ...isibilityDisabledByConventionController.cs | 2 +- ...VisibilityEnabledByConventionController.cs | 2 +- ...plorerVisibilitySetExplicitlyController.cs | 2 +- .../ApiExplorerWebSite/Models/Customer.cs | 2 +- .../ApiExplorerWebSite/Models/Product.cs | 2 +- .../ProducesTypeAttribute.cs | 2 +- test/WebSites/ApiExplorerWebSite/Startup.cs | 2 +- .../CompositeViewEngineWebSite.kproj} | 0 .../HomeController.cs | 2 +- .../Startup.cs | 2 +- .../TestPartialView.cs | 2 +- .../TestView.cs | 2 +- .../TestViewEngine.cs | 2 +- .../Views/Home/Index.cshtml | 0 .../project.json | 0 .../wwwroot/readme.md | Bin .../FallbackOnTypeBasedMatchController.cs | 2 +- .../Controllers/HomeController.cs | 2 +- .../Controllers/JsonResultController.cs | 2 +- .../Controllers/NoContentController.cs | 2 +- ...oNotTreatNullValueAsNoContentController.cs | 2 +- .../NoProducesContentOnClassController.cs | 2 +- .../Controllers/NormalController.cs | 2 +- .../ProducesContentBaseController.cs | 2 +- .../ProducesContentOnClassController.cs | 2 +- ...oducesWithMediaTypeParametersController.cs | 4 +- .../Controllers/TextPlainController.cs | 2 +- .../WebSites/ConnegWebSite/CustomFormatter.cs | 2 +- test/WebSites/ConnegWebSite/Models/Contact.cs | 2 +- test/WebSites/ConnegWebSite/Models/User.cs | 2 +- .../ConnegWebSite/PlainTextFormatter.cs | 2 +- test/WebSites/ConnegWebSite/Startup.cs | 2 +- .../ConnegWebSite/VCardFormatter_V3.cs | 4 +- .../ConnegWebSite/VCardFormatter_V4.cs | 4 +- .../CustomValueProviderFactory.cs | 2 +- .../FlagsEnum.cs | 2 +- .../HomeController.cs | 2 +- .../Startup.cs | 2 +- .../ValueProvidersWebSite.kproj} | 0 .../project.json | 0 .../wwwroot/readme.md | Bin 60 files changed, 98 insertions(+), 94 deletions(-) rename test/WebSites/{CompositeViewEngine/CompositeViewEngine.kproj => CompositeViewEngineWebSite/CompositeViewEngineWebSite.kproj} (100%) rename test/WebSites/{CompositeViewEngine => CompositeViewEngineWebSite}/HomeController.cs (92%) rename test/WebSites/{CompositeViewEngine => CompositeViewEngineWebSite}/Startup.cs (96%) rename test/WebSites/{CompositeViewEngine => CompositeViewEngineWebSite}/TestPartialView.cs (92%) rename test/WebSites/{CompositeViewEngine => CompositeViewEngineWebSite}/TestView.cs (92%) rename test/WebSites/{CompositeViewEngine => CompositeViewEngineWebSite}/TestViewEngine.cs (96%) rename test/WebSites/{CompositeViewEngine => CompositeViewEngineWebSite}/Views/Home/Index.cshtml (100%) rename test/WebSites/{CompositeViewEngine => CompositeViewEngineWebSite}/project.json (100%) rename test/WebSites/{CompositeViewEngine => CompositeViewEngineWebSite}/wwwroot/readme.md (100%) rename test/WebSites/{ValueProvidersSite => ValueProvidersWebSite}/CustomValueProviderFactory.cs (97%) rename test/WebSites/{ValueProvidersSite => ValueProvidersWebSite}/FlagsEnum.cs (91%) rename test/WebSites/{ValueProvidersSite => ValueProvidersWebSite}/HomeController.cs (96%) rename test/WebSites/{ValueProvidersSite => ValueProvidersWebSite}/Startup.cs (96%) rename test/WebSites/{ValueProvidersSite/ValueProvidersSite.kproj => ValueProvidersWebSite/ValueProvidersWebSite.kproj} (100%) rename test/WebSites/{ValueProvidersSite => ValueProvidersWebSite}/project.json (100%) rename test/WebSites/{ValueProvidersSite => ValueProvidersWebSite}/wwwroot/readme.md (100%) diff --git a/Mvc.sln b/Mvc.sln index 63f283dac8..82092028de 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22213.0 +VisualStudioVersion = 14.0.22410.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" EndProject @@ -49,14 +49,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RoutingWebSite", "test\WebS EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Test", "test\Microsoft.AspNet.Mvc.Test\Microsoft.AspNet.Mvc.Test.kproj", "{5F945B82-FE5F-425C-956C-8BC2F2020254}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CompositeViewEngine", "test\WebSites\CompositeViewEngine\CompositeViewEngine.kproj", "{A853B2BA-4449-4908-A416-5A3C027FC22B}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorWebSite", "test\WebSites\RazorWebSite\RazorWebSite.kproj", "{B07CAF59-11ED-40E3-A5DB-E1178F84FA78}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "FormatterWebSite", "test\WebSites\FormatterWebSite\FormatterWebSite.kproj", "{62735776-46FF-4170-9392-02E128A69B89}" EndProject -Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ValueProvidersSite", "test\WebSites\ValueProvidersSite\ValueProvidersSite.kproj", "{14F79E79-AE79-48FA-95DE-D794EF4EABB3}" -EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.HeaderValueAbstractions", "src\Microsoft.AspNet.Mvc.HeaderValueAbstractions\Microsoft.AspNet.Mvc.HeaderValueAbstractions.kproj", "{98335B23-E4B9-4CAD-9749-0DED32A659A1}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.HeaderValueAbstractions.Tests", "test\Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test\Microsoft.AspNet.Mvc.HeaderValueAbstractions.Tests.kproj", "{E69FD235-2042-43A4-9970-59CB29955B4E}" @@ -110,6 +106,10 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PrecompilationWebSite", "te EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RequestServicesWebSite", "test\WebSites\RequestServicesWebSite\RequestServicesWebSite.kproj", "{F12E9CF0-4958-40C6-A6CD-759185157F84}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -310,16 +310,6 @@ Global {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|Mixed Platforms.Build.0 = Release|Any CPU {5F945B82-FE5F-425C-956C-8BC2F2020254}.Release|x86.ActiveCfg = Release|Any CPU - {A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|x86.ActiveCfg = Debug|Any CPU - {A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Any CPU.Build.0 = Release|Any CPU - {A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {A853B2BA-4449-4908-A416-5A3C027FC22B}.Release|x86.ActiveCfg = Release|Any CPU {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Any CPU.Build.0 = Debug|Any CPU {B07CAF59-11ED-40E3-A5DB-E1178F84FA78}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -340,16 +330,6 @@ Global {62735776-46FF-4170-9392-02E128A69B89}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {62735776-46FF-4170-9392-02E128A69B89}.Release|Mixed Platforms.Build.0 = Release|Any CPU {62735776-46FF-4170-9392-02E128A69B89}.Release|x86.ActiveCfg = Release|Any CPU - {14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Debug|x86.ActiveCfg = Debug|Any CPU - {14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Any CPU.Build.0 = Release|Any CPU - {14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {14F79E79-AE79-48FA-95DE-D794EF4EABB3}.Release|x86.ActiveCfg = Release|Any CPU {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Any CPU.Build.0 = Debug|Any CPU {98335B23-E4B9-4CAD-9749-0DED32A659A1}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -590,6 +570,30 @@ Global {F12E9CF0-4958-40C6-A6CD-759185157F84}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {F12E9CF0-4958-40C6-A6CD-759185157F84}.Release|Mixed Platforms.Build.0 = Release|Any CPU {F12E9CF0-4958-40C6-A6CD-759185157F84}.Release|x86.ActiveCfg = Release|Any CPU + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -615,10 +619,8 @@ Global {680D75ED-601F-4D86-B01B-1072D0C31B8C} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {42CDBF4A-E238-4C0F-A416-44588363EB4C} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {5F945B82-FE5F-425C-956C-8BC2F2020254} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} - {A853B2BA-4449-4908-A416-5A3C027FC22B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {B07CAF59-11ED-40E3-A5DB-E1178F84FA78} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {62735776-46FF-4170-9392-02E128A69B89} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} - {14F79E79-AE79-48FA-95DE-D794EF4EABB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {98335B23-E4B9-4CAD-9749-0DED32A659A1} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {E69FD235-2042-43A4-9970-59CB29955B4E} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {EE1AB716-F102-4CA3-AD2C-214A44B459A0} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} @@ -643,5 +645,7 @@ Global {860119ED-3DB1-424D-8D0A-30132A8A7D96} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {59E1BE90-92C1-4D35-ADCC-B69F49077C81} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {F12E9CF0-4958-40C6-A6CD-759185157F84} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {A853B2BA-4449-4908-A416-5A3C027FC22B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {14F79E79-AE79-48FA-95DE-D794EF4EABB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} EndGlobalSection EndGlobal diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs index b167f31292..fb8ed2050f 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public class ApiExplorerTest { private readonly IServiceProvider _provider = TestHelper.CreateServices("ApiExplorerWebSite"); - private readonly Action _app = new ApiExplorer.Startup().Configure; + private readonly Action _app = new ApiExplorerWebSite.Startup().Configure; [Fact] public async Task ApiExplorer_IsVisible_EnabledWithConvention() @@ -498,9 +498,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests } [Theory] - [InlineData("GetProduct", "ApiExplorer.Product")] + [InlineData("GetProduct", "ApiExplorerWebSite.Product")] [InlineData("GetInt", "System.Int32")] - [InlineData("GetTaskOfProduct", "ApiExplorer.Product")] + [InlineData("GetTaskOfProduct", "ApiExplorerWebSite.Product")] [InlineData("GetTaskOfInt", "System.Int32")] public async Task ApiExplorer_ResponseType_KnownWithoutAttribute(string action, string type) { @@ -521,10 +521,10 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests } [Theory] - [InlineData("GetVoid", "ApiExplorer.Customer")] - [InlineData("GetObject", "ApiExplorer.Product")] + [InlineData("GetVoid", "ApiExplorerWebSite.Customer")] + [InlineData("GetObject", "ApiExplorerWebSite.Product")] [InlineData("GetIActionResult", "System.String")] - [InlineData("GetProduct", "ApiExplorer.Customer")] + [InlineData("GetProduct", "ApiExplorerWebSite.Customer")] [InlineData("GetTask", "System.Int32")] public async Task ApiExplorer_ResponseType_KnownWithAttribute(string action, string type) { @@ -545,8 +545,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests } [Theory] - [InlineData("Controller", "ApiExplorer.Product")] - [InlineData("Action", "ApiExplorer.Customer")] + [InlineData("Controller", "ApiExplorerWebSite.Product")] + [InlineData("Action", "ApiExplorerWebSite.Customer")] public async Task ApiExplorer_ResponseType_OverrideOnAction(string action, string type) { // Arrange diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/CompositeViewEngineTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/CompositeViewEngineTests.cs index 49ff7545cd..82cd75a224 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/CompositeViewEngineTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/CompositeViewEngineTests.cs @@ -11,8 +11,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { public class CompositeViewEngineTests { - private readonly IServiceProvider _services = TestHelper.CreateServices("CompositeViewEngine"); - private readonly Action _app = new CompositeViewEngine.Startup().Configure; + private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(CompositeViewEngineWebSite)); + private readonly Action _app = new CompositeViewEngineWebSite.Startup().Configure; [Fact] public async Task CompositeViewEngine_FindsPartialViewsAcrossAllEngines() diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs index ca20a57065..0d023c8a02 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs @@ -7,7 +7,7 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; -using ConnegWebsite; +using ConnegWebSite; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; using Xunit; @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { public class ConnegTests { - private readonly IServiceProvider _provider = TestHelper.CreateServices("ConnegWebSite"); + private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(ConnegWebSite)); private readonly Action _app = new Startup().Configure; [Fact] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/OutputFormatterTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/OutputFormatterTest.cs index 5b73b0a87c..6161e3e626 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/OutputFormatterTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/OutputFormatterTest.cs @@ -5,7 +5,7 @@ using System; using System.Net; using System.Net.Http.Headers; using System.Threading.Tasks; -using ConnegWebsite; +using ConnegWebSite; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; using Xunit; @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { public class OutputFormatterTest { - private readonly IServiceProvider _provider = TestHelper.CreateServices("ConnegWebSite"); + private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(ConnegWebSite)); private readonly Action _app = new Startup().Configure; [Theory] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ValueProviderTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ValueProviderTest.cs index 75b317d3cf..166d49e4ed 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ValueProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ValueProviderTest.cs @@ -5,14 +5,14 @@ using System; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; -using ValueProvidersSite; +using ValueProvidersWebSite; using Xunit; namespace Microsoft.AspNet.Mvc.FunctionalTests { public class ValueProviderTest { - private readonly IServiceProvider _services = TestHelper.CreateServices("ValueProvidersSite"); + private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(ValueProvidersWebSite)); private readonly Action _app = new Startup().Configure; [Fact] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json index d7faa63abc..6f3b83e729 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json @@ -9,7 +9,7 @@ "ApiExplorerWebSite": "1.0.0", "ApplicationModelWebSite": "1.0.0", "BasicWebSite": "1.0.0", - "CompositeViewEngine": "1.0.0", + "CompositeViewEngineWebSite": "1.0.0", "ConnegWebSite": "1.0.0", "FilesWebSite": "1.0.0", "FiltersWebSite": "1.0.0", @@ -25,7 +25,7 @@ "TagHelperSample.Web": "1.0.0", "TagHelpersWebSite": "1.0.0", "UrlHelperWebSite": "1.0.0", - "ValueProvidersSite": "1.0.0", + "ValueProvidersWebSite": "1.0.0", "VersioningWebSite": "1.0.0", "ViewComponentWebSite": "1.0.0", "XmlSerializerWebSite": "1.0.0", diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs index bab7c29a59..a0e2001fed 100644 --- a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs +++ b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs @@ -6,7 +6,7 @@ using System.Linq; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Description; -namespace ApiExplorer +namespace ApiExplorerWebSite { /// /// An action filter that looks up and serializes Api Explorer data for the action. diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityDisabledConvention.cs b/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityDisabledConvention.cs index 2b1daf27b4..5840722d8d 100644 --- a/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityDisabledConvention.cs +++ b/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityDisabledConvention.cs @@ -5,7 +5,7 @@ using System; using System.Reflection; using Microsoft.AspNet.Mvc.ApplicationModels; -namespace ApiExplorer +namespace ApiExplorerWebSite { // Disables ApiExplorer for a specific controller type. // This is part of the test that validates that ApiExplorer can be configured via diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityEnabledConvention.cs b/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityEnabledConvention.cs index 664dd95d72..7e06902d8f 100644 --- a/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityEnabledConvention.cs +++ b/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityEnabledConvention.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc.ApplicationModels; -namespace ApiExplorer +namespace ApiExplorerWebSite { // Enables ApiExplorer for controllers that haven't explicitly configured it. // This is part of the test that validates that ApiExplorer can be configured via diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerHttpMethodController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerHttpMethodController.cs index 6c64202261..3c86642bf4 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerHttpMethodController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerHttpMethodController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [Route("ApiExplorerHttpMethod")] public class ApiExplorerHttpMethodController : Controller diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetByConventionController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetByConventionController.cs index d5aeda810a..6f2a8d760d 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetByConventionController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetByConventionController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [Route("ApiExplorerNameSetByConvention")] public class ApiExplorerNameSetByConventionController : Controller diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetExplicitlyController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetExplicitlyController.cs index a6e49ad5d9..db923f4977 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetExplicitlyController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetExplicitlyController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [ApiExplorerSettings(GroupName = "SetOnController")] [Route("ApiExplorerNameSetExplicitly")] diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeController.cs index b1d6771c30..25b22422f5 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [Route("ApiExplorerResponseContentType/[Action]")] public class ApiExplorerResponseContentTypeController : Controller diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeOverrideOnActionController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeOverrideOnActionController.cs index 6e6bd35728..3ed9483fed 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeOverrideOnActionController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeOverrideOnActionController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [Produces("text/xml")] [Route("ApiExplorerResponseContentTypeOverrideOnAction")] diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs index 3b7c178cfe..f457dfcf56 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [Produces("*/*", Type = typeof(Product))] [Route("ApiExplorerResponseTypeOverrideOnAction")] diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithAttributeController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithAttributeController.cs index 59589cb578..29c9cd7a2f 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithAttributeController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithAttributeController.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [Route("ApiExplorerResponseTypeWithAttribute/[Action]")] public class ApiExplorerResponseTypeWithAttributeController : Controller diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs index 4bbd53da60..813acf4d4e 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [Route("ApiExplorerResponseTypeWithoutAttribute/[Action]")] public class ApiExplorerResponseTypeWithoutAttributeController : Controller diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerRouteAndPathParametersInformationController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerRouteAndPathParametersInformationController.cs index 16036e0b65..798bd7937f 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerRouteAndPathParametersInformationController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerRouteAndPathParametersInformationController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [Route("ApiExplorerRouteAndPathParametersInformation")] public class ApiExplorerRouteAndPathParametersInformationController diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs index 43c677b106..7a535ae18f 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [Route("ApiExplorerVisbilityDisabledByConvention")] public class ApiExplorerVisbilityDisabledByConventionController : Controller diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs index bf9c9f695f..acd0c62ae8 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [Route("ApiExplorerVisbilityEnabledByConvention")] public class ApiExplorerVisbilityEnabledByConventionController : Controller diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilitySetExplicitlyController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilitySetExplicitlyController.cs index bf6d6b9b8c..d6503cd940 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilitySetExplicitlyController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilitySetExplicitlyController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ApiExplorer +namespace ApiExplorerWebSite { [ApiExplorerSettings(IgnoreApi = true)] [Route("ApiExplorerVisibilitySetExplicitly")] diff --git a/test/WebSites/ApiExplorerWebSite/Models/Customer.cs b/test/WebSites/ApiExplorerWebSite/Models/Customer.cs index 161b1d8039..07d993ba01 100644 --- a/test/WebSites/ApiExplorerWebSite/Models/Customer.cs +++ b/test/WebSites/ApiExplorerWebSite/Models/Customer.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace ApiExplorer +namespace ApiExplorerWebSite { public class Customer { diff --git a/test/WebSites/ApiExplorerWebSite/Models/Product.cs b/test/WebSites/ApiExplorerWebSite/Models/Product.cs index af1d536275..2d608bedd2 100644 --- a/test/WebSites/ApiExplorerWebSite/Models/Product.cs +++ b/test/WebSites/ApiExplorerWebSite/Models/Product.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace ApiExplorer +namespace ApiExplorerWebSite { public class Product { diff --git a/test/WebSites/ApiExplorerWebSite/ProducesTypeAttribute.cs b/test/WebSites/ApiExplorerWebSite/ProducesTypeAttribute.cs index fea0b4f31c..f9150e603d 100644 --- a/test/WebSites/ApiExplorerWebSite/ProducesTypeAttribute.cs +++ b/test/WebSites/ApiExplorerWebSite/ProducesTypeAttribute.cs @@ -7,7 +7,7 @@ using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Description; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; -namespace ApiExplorer +namespace ApiExplorerWebSite { public class ProducesTypeAttribute : ResultFilterAttribute, IApiResponseMetadataProvider { diff --git a/test/WebSites/ApiExplorerWebSite/Startup.cs b/test/WebSites/ApiExplorerWebSite/Startup.cs index 33db37168a..34b77d4717 100644 --- a/test/WebSites/ApiExplorerWebSite/Startup.cs +++ b/test/WebSites/ApiExplorerWebSite/Startup.cs @@ -6,7 +6,7 @@ using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; -namespace ApiExplorer +namespace ApiExplorerWebSite { public class Startup { diff --git a/test/WebSites/CompositeViewEngine/CompositeViewEngine.kproj b/test/WebSites/CompositeViewEngineWebSite/CompositeViewEngineWebSite.kproj similarity index 100% rename from test/WebSites/CompositeViewEngine/CompositeViewEngine.kproj rename to test/WebSites/CompositeViewEngineWebSite/CompositeViewEngineWebSite.kproj diff --git a/test/WebSites/CompositeViewEngine/HomeController.cs b/test/WebSites/CompositeViewEngineWebSite/HomeController.cs similarity index 92% rename from test/WebSites/CompositeViewEngine/HomeController.cs rename to test/WebSites/CompositeViewEngineWebSite/HomeController.cs index d79da054db..f743352136 100644 --- a/test/WebSites/CompositeViewEngine/HomeController.cs +++ b/test/WebSites/CompositeViewEngineWebSite/HomeController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace CompositeViewEngine +namespace CompositeViewEngineWebSite { public class HomeController { diff --git a/test/WebSites/CompositeViewEngine/Startup.cs b/test/WebSites/CompositeViewEngineWebSite/Startup.cs similarity index 96% rename from test/WebSites/CompositeViewEngine/Startup.cs rename to test/WebSites/CompositeViewEngineWebSite/Startup.cs index 389a6b3082..8dec22ad38 100644 --- a/test/WebSites/CompositeViewEngine/Startup.cs +++ b/test/WebSites/CompositeViewEngineWebSite/Startup.cs @@ -5,7 +5,7 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; using Microsoft.Framework.DependencyInjection; -namespace CompositeViewEngine +namespace CompositeViewEngineWebSite { public class Startup { diff --git a/test/WebSites/CompositeViewEngine/TestPartialView.cs b/test/WebSites/CompositeViewEngineWebSite/TestPartialView.cs similarity index 92% rename from test/WebSites/CompositeViewEngine/TestPartialView.cs rename to test/WebSites/CompositeViewEngineWebSite/TestPartialView.cs index 2a82cc966e..264fec5e09 100644 --- a/test/WebSites/CompositeViewEngine/TestPartialView.cs +++ b/test/WebSites/CompositeViewEngineWebSite/TestPartialView.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Rendering; -namespace CompositeViewEngine +namespace CompositeViewEngineWebSite { public class TestPartialView : IView { diff --git a/test/WebSites/CompositeViewEngine/TestView.cs b/test/WebSites/CompositeViewEngineWebSite/TestView.cs similarity index 92% rename from test/WebSites/CompositeViewEngine/TestView.cs rename to test/WebSites/CompositeViewEngineWebSite/TestView.cs index 6a191a299f..907c37aaf2 100644 --- a/test/WebSites/CompositeViewEngine/TestView.cs +++ b/test/WebSites/CompositeViewEngineWebSite/TestView.cs @@ -5,7 +5,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Rendering; -namespace CompositeViewEngine +namespace CompositeViewEngineWebSite { public class TestView : IView { diff --git a/test/WebSites/CompositeViewEngine/TestViewEngine.cs b/test/WebSites/CompositeViewEngineWebSite/TestViewEngine.cs similarity index 96% rename from test/WebSites/CompositeViewEngine/TestViewEngine.cs rename to test/WebSites/CompositeViewEngineWebSite/TestViewEngine.cs index 7b59d40b0a..6e33536211 100644 --- a/test/WebSites/CompositeViewEngine/TestViewEngine.cs +++ b/test/WebSites/CompositeViewEngineWebSite/TestViewEngine.cs @@ -5,7 +5,7 @@ using System; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Rendering; -namespace CompositeViewEngine +namespace CompositeViewEngineWebSite { public class TestViewEngine : IViewEngine { diff --git a/test/WebSites/CompositeViewEngine/Views/Home/Index.cshtml b/test/WebSites/CompositeViewEngineWebSite/Views/Home/Index.cshtml similarity index 100% rename from test/WebSites/CompositeViewEngine/Views/Home/Index.cshtml rename to test/WebSites/CompositeViewEngineWebSite/Views/Home/Index.cshtml diff --git a/test/WebSites/CompositeViewEngine/project.json b/test/WebSites/CompositeViewEngineWebSite/project.json similarity index 100% rename from test/WebSites/CompositeViewEngine/project.json rename to test/WebSites/CompositeViewEngineWebSite/project.json diff --git a/test/WebSites/CompositeViewEngine/wwwroot/readme.md b/test/WebSites/CompositeViewEngineWebSite/wwwroot/readme.md similarity index 100% rename from test/WebSites/CompositeViewEngine/wwwroot/readme.md rename to test/WebSites/CompositeViewEngineWebSite/wwwroot/readme.md diff --git a/test/WebSites/ConnegWebSite/Controllers/FallbackOnTypeBasedMatchController.cs b/test/WebSites/ConnegWebSite/Controllers/FallbackOnTypeBasedMatchController.cs index d49d693d53..90d724afdd 100644 --- a/test/WebSites/ConnegWebSite/Controllers/FallbackOnTypeBasedMatchController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/FallbackOnTypeBasedMatchController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; using Microsoft.Framework.DependencyInjection; -namespace ConnegWebsite +namespace ConnegWebSite { public class FallbackOnTypeBasedMatchController : Controller { diff --git a/test/WebSites/ConnegWebSite/Controllers/HomeController.cs b/test/WebSites/ConnegWebSite/Controllers/HomeController.cs index ec92463c3b..56307aa2fe 100644 --- a/test/WebSites/ConnegWebSite/Controllers/HomeController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/HomeController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ConnegWebsite +namespace ConnegWebSite { public class HomeController : Controller { diff --git a/test/WebSites/ConnegWebSite/Controllers/JsonResultController.cs b/test/WebSites/ConnegWebSite/Controllers/JsonResultController.cs index bef9e38a78..370371b382 100644 --- a/test/WebSites/ConnegWebSite/Controllers/JsonResultController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/JsonResultController.cs @@ -4,7 +4,7 @@ using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; -namespace ConnegWebsite +namespace ConnegWebSite { public class JsonResultController : Controller { diff --git a/test/WebSites/ConnegWebSite/Controllers/NoContentController.cs b/test/WebSites/ConnegWebSite/Controllers/NoContentController.cs index b2019243b8..9cd8902575 100644 --- a/test/WebSites/ConnegWebSite/Controllers/NoContentController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/NoContentController.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Mvc; -namespace ConnegWebsite +namespace ConnegWebSite { public class NoContentController : Controller { diff --git a/test/WebSites/ConnegWebSite/Controllers/NoContentDoNotTreatNullValueAsNoContentController.cs b/test/WebSites/ConnegWebSite/Controllers/NoContentDoNotTreatNullValueAsNoContentController.cs index ed7bd468e9..8a16c2d1db 100644 --- a/test/WebSites/ConnegWebSite/Controllers/NoContentDoNotTreatNullValueAsNoContentController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/NoContentDoNotTreatNullValueAsNoContentController.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Mvc; -namespace ConnegWebsite +namespace ConnegWebSite { public class NoContentDoNotTreatNullValueAsNoContentController : Controller { diff --git a/test/WebSites/ConnegWebSite/Controllers/NoProducesContentOnClassController.cs b/test/WebSites/ConnegWebSite/Controllers/NoProducesContentOnClassController.cs index 570a63a2b1..a223670a12 100644 --- a/test/WebSites/ConnegWebSite/Controllers/NoProducesContentOnClassController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/NoProducesContentOnClassController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ConnegWebsite +namespace ConnegWebSite { public class NoProducesContentOnClassController : ProducesContentBaseController { diff --git a/test/WebSites/ConnegWebSite/Controllers/NormalController.cs b/test/WebSites/ConnegWebSite/Controllers/NormalController.cs index 0979c844b0..3ac7fb5d14 100644 --- a/test/WebSites/ConnegWebSite/Controllers/NormalController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/NormalController.cs @@ -4,7 +4,7 @@ using Microsoft.AspNet.Mvc; using Newtonsoft.Json; -namespace ConnegWebsite +namespace ConnegWebSite { public class NormalController : Controller { diff --git a/test/WebSites/ConnegWebSite/Controllers/ProducesContentBaseController.cs b/test/WebSites/ConnegWebSite/Controllers/ProducesContentBaseController.cs index 877666026a..2e35e28a32 100644 --- a/test/WebSites/ConnegWebSite/Controllers/ProducesContentBaseController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/ProducesContentBaseController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ConnegWebsite +namespace ConnegWebSite { [Produces("application/custom_ProducesContentBaseController")] public class ProducesContentBaseController : Controller diff --git a/test/WebSites/ConnegWebSite/Controllers/ProducesContentOnClassController.cs b/test/WebSites/ConnegWebSite/Controllers/ProducesContentOnClassController.cs index f899c63d30..9b0acbdd10 100644 --- a/test/WebSites/ConnegWebSite/Controllers/ProducesContentOnClassController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/ProducesContentOnClassController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ConnegWebsite +namespace ConnegWebSite { [Produces("application/custom_ProducesContentOnClassController")] public class ProducesContentOnClassController : ProducesContentBaseController diff --git a/test/WebSites/ConnegWebSite/Controllers/ProducesWithMediaTypeParametersController.cs b/test/WebSites/ConnegWebSite/Controllers/ProducesWithMediaTypeParametersController.cs index e87db2ffa9..8e2da1a87a 100644 --- a/test/WebSites/ConnegWebSite/Controllers/ProducesWithMediaTypeParametersController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/ProducesWithMediaTypeParametersController.cs @@ -1,10 +1,10 @@ // 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 ConnegWebsite.Models; +using ConnegWebSite.Models; using Microsoft.AspNet.Mvc; -namespace ConnegWebsite +namespace ConnegWebSite { public class ProducesWithMediaTypeParametersController : Controller { diff --git a/test/WebSites/ConnegWebSite/Controllers/TextPlainController.cs b/test/WebSites/ConnegWebSite/Controllers/TextPlainController.cs index 1057cdd4fd..4d94f88b82 100644 --- a/test/WebSites/ConnegWebSite/Controllers/TextPlainController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/TextPlainController.cs @@ -4,7 +4,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.Mvc; -namespace ConnegWebsite +namespace ConnegWebSite { public class TextPlainController : Controller { diff --git a/test/WebSites/ConnegWebSite/CustomFormatter.cs b/test/WebSites/ConnegWebSite/CustomFormatter.cs index b33cb21742..a80666b4b6 100644 --- a/test/WebSites/ConnegWebSite/CustomFormatter.cs +++ b/test/WebSites/ConnegWebSite/CustomFormatter.cs @@ -7,7 +7,7 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; -namespace ConnegWebsite +namespace ConnegWebSite { public class CustomFormatter : OutputFormatter { diff --git a/test/WebSites/ConnegWebSite/Models/Contact.cs b/test/WebSites/ConnegWebSite/Models/Contact.cs index 287eb5e444..c2b31f71f0 100644 --- a/test/WebSites/ConnegWebSite/Models/Contact.cs +++ b/test/WebSites/ConnegWebSite/Models/Contact.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -namespace ConnegWebsite.Models +namespace ConnegWebSite.Models { public class Contact { diff --git a/test/WebSites/ConnegWebSite/Models/User.cs b/test/WebSites/ConnegWebSite/Models/User.cs index cb32ddbcfc..b52467214d 100644 --- a/test/WebSites/ConnegWebSite/Models/User.cs +++ b/test/WebSites/ConnegWebSite/Models/User.cs @@ -3,7 +3,7 @@ using System.ComponentModel.DataAnnotations; -namespace ConnegWebsite +namespace ConnegWebSite { [DisplayColumn("Name")] public class User diff --git a/test/WebSites/ConnegWebSite/PlainTextFormatter.cs b/test/WebSites/ConnegWebSite/PlainTextFormatter.cs index c812bdb979..c8f6bd1520 100644 --- a/test/WebSites/ConnegWebSite/PlainTextFormatter.cs +++ b/test/WebSites/ConnegWebSite/PlainTextFormatter.cs @@ -7,7 +7,7 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; -namespace ConnegWebsite +namespace ConnegWebSite { public class PlainTextFormatter : OutputFormatter { diff --git a/test/WebSites/ConnegWebSite/Startup.cs b/test/WebSites/ConnegWebSite/Startup.cs index 939707b984..8901654343 100644 --- a/test/WebSites/ConnegWebSite/Startup.cs +++ b/test/WebSites/ConnegWebSite/Startup.cs @@ -5,7 +5,7 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; -namespace ConnegWebsite +namespace ConnegWebSite { public class Startup { diff --git a/test/WebSites/ConnegWebSite/VCardFormatter_V3.cs b/test/WebSites/ConnegWebSite/VCardFormatter_V3.cs index fbcb6af7f2..ca7e84710a 100644 --- a/test/WebSites/ConnegWebSite/VCardFormatter_V3.cs +++ b/test/WebSites/ConnegWebSite/VCardFormatter_V3.cs @@ -6,11 +6,11 @@ using System.IO; using System.Reflection; using System.Text; using System.Threading.Tasks; -using ConnegWebsite.Models; +using ConnegWebSite.Models; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; -namespace ConnegWebsite +namespace ConnegWebSite { /// /// Provides contact information of a person through VCard format. diff --git a/test/WebSites/ConnegWebSite/VCardFormatter_V4.cs b/test/WebSites/ConnegWebSite/VCardFormatter_V4.cs index a77f02a176..2b6b549afb 100644 --- a/test/WebSites/ConnegWebSite/VCardFormatter_V4.cs +++ b/test/WebSites/ConnegWebSite/VCardFormatter_V4.cs @@ -6,11 +6,11 @@ using System.IO; using System.Reflection; using System.Text; using System.Threading.Tasks; -using ConnegWebsite.Models; +using ConnegWebSite.Models; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.HeaderValueAbstractions; -namespace ConnegWebsite +namespace ConnegWebSite { /// /// Provides contact information of a person through VCard format. diff --git a/test/WebSites/ValueProvidersSite/CustomValueProviderFactory.cs b/test/WebSites/ValueProvidersWebSite/CustomValueProviderFactory.cs similarity index 97% rename from test/WebSites/ValueProvidersSite/CustomValueProviderFactory.cs rename to test/WebSites/ValueProvidersWebSite/CustomValueProviderFactory.cs index 2c482ace11..0973c9624c 100644 --- a/test/WebSites/ValueProvidersSite/CustomValueProviderFactory.cs +++ b/test/WebSites/ValueProvidersWebSite/CustomValueProviderFactory.cs @@ -6,7 +6,7 @@ using System.Globalization; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding; -namespace ValueProvidersSite +namespace ValueProvidersWebSite { public class CustomValueProviderFactory : IValueProviderFactory { diff --git a/test/WebSites/ValueProvidersSite/FlagsEnum.cs b/test/WebSites/ValueProvidersWebSite/FlagsEnum.cs similarity index 91% rename from test/WebSites/ValueProvidersSite/FlagsEnum.cs rename to test/WebSites/ValueProvidersWebSite/FlagsEnum.cs index 766ac8f6b4..7ecfb95166 100644 --- a/test/WebSites/ValueProvidersSite/FlagsEnum.cs +++ b/test/WebSites/ValueProvidersWebSite/FlagsEnum.cs @@ -3,7 +3,7 @@ using System; -namespace ValueProvidersSite +namespace ValueProvidersWebSite { [Flags] public enum FlagsEnum diff --git a/test/WebSites/ValueProvidersSite/HomeController.cs b/test/WebSites/ValueProvidersWebSite/HomeController.cs similarity index 96% rename from test/WebSites/ValueProvidersSite/HomeController.cs rename to test/WebSites/ValueProvidersWebSite/HomeController.cs index 12147841f8..e23d703c05 100644 --- a/test/WebSites/ValueProvidersSite/HomeController.cs +++ b/test/WebSites/ValueProvidersWebSite/HomeController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNet.Mvc; -namespace ValueProvidersSite +namespace ValueProvidersWebSite { public class HomeController { diff --git a/test/WebSites/ValueProvidersSite/Startup.cs b/test/WebSites/ValueProvidersWebSite/Startup.cs similarity index 96% rename from test/WebSites/ValueProvidersSite/Startup.cs rename to test/WebSites/ValueProvidersWebSite/Startup.cs index 1d028525e5..b833dd39bb 100644 --- a/test/WebSites/ValueProvidersSite/Startup.cs +++ b/test/WebSites/ValueProvidersWebSite/Startup.cs @@ -5,7 +5,7 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; using Microsoft.Framework.DependencyInjection; -namespace ValueProvidersSite +namespace ValueProvidersWebSite { public class Startup { diff --git a/test/WebSites/ValueProvidersSite/ValueProvidersSite.kproj b/test/WebSites/ValueProvidersWebSite/ValueProvidersWebSite.kproj similarity index 100% rename from test/WebSites/ValueProvidersSite/ValueProvidersSite.kproj rename to test/WebSites/ValueProvidersWebSite/ValueProvidersWebSite.kproj diff --git a/test/WebSites/ValueProvidersSite/project.json b/test/WebSites/ValueProvidersWebSite/project.json similarity index 100% rename from test/WebSites/ValueProvidersSite/project.json rename to test/WebSites/ValueProvidersWebSite/project.json diff --git a/test/WebSites/ValueProvidersSite/wwwroot/readme.md b/test/WebSites/ValueProvidersWebSite/wwwroot/readme.md similarity index 100% rename from test/WebSites/ValueProvidersSite/wwwroot/readme.md rename to test/WebSites/ValueProvidersWebSite/wwwroot/readme.md From f338f70e06a7450a53e8951d9385ac44fd1e668c Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 10 Dec 2014 15:52:22 -0800 Subject: [PATCH 005/118] Update WebAPI shim functional tests to use a header for payload The issue is that responses to HEAD cannot have a body. When running these tests on a real server, they break because the server throws when you try to write to the body. --- .../WebApiCompatShimActionSelectionTest.cs | 120 +++++++++--------- .../ActionSelectionFilter.cs | 18 ++- 2 files changed, 73 insertions(+), 65 deletions(-) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs index 217f33aa06..4731677348 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { public class WebApiCompatShimActionSelectionTest { - private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(WebApiCompatShimWebSite)); + private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(WebApiCompatShimWebSite)); private readonly Action _app = new WebApiCompatShimWebSite.Startup().Configure; [Theory] @@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_TakesHttpMethodFromPrefix_UnnamedAction(string httpMethod, string actionName) { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -37,9 +37,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_TakesHttpMethodFromPrefix_NamedAction(string httpMethod, string actionName) { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -66,9 +66,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -79,7 +79,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_TakesHttpMethodFromPrefix_NamedAction_MismatchedVerb() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -97,7 +97,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_TakesHttpMethodFromPrefix_UnnamedAction_DefaultVerbIsPost_Success() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -106,9 +106,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -119,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_TakesHttpMethodFromPrefix_NamedAction_DefaultVerbIsPost_Success() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -128,9 +128,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -141,7 +141,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_TakesHttpMethodFromPrefix_UnnamedAction_DefaultVerbIsPost_VerbMismatch() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -159,7 +159,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_TakesHttpMethodFromPrefix_NamedAction_DefaultVerbIsPost_VerbMismatch() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -177,7 +177,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_TakesHttpMethodFromMethodName_NotActionName_UnnamedAction_Success() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -186,9 +186,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -199,7 +199,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_TakesHttpMethodFromMethodName_NotActionName_NamedAction_Success() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -208,9 +208,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -221,7 +221,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_TakesHttpMethodFromMethodName_NotActionName_UnnamedAction_VerbMismatch() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -239,7 +239,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_TakesHttpMethodFromMethodName_NotActionName_NamedAction_VerbMismatch() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -257,7 +257,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_HttpMethodOverride_UnnamedAction_Success() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -266,9 +266,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -279,7 +279,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_HttpMethodOverride_NamedAction_Success() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -288,9 +288,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -301,7 +301,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_HttpMethodOverride_UnnamedAction_VerbMismatch() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -319,7 +319,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task WebAPIConvention_HttpMethodOverride_NamedAction_VerbMismatch() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage( @@ -368,16 +368,16 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task LegacyActionSelection_OverloadedAction_WithUnnamedAction(string httpMethod, string requestUrl, string expectedActionName) { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl); // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -395,16 +395,16 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task LegacyActionSelection_OverloadedAction_NonIdRouteParameter(string httpMethod, string requestUrl, string expectedActionName) { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl); // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -419,16 +419,16 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task LegacyActionSelection_OverloadedAction_Parameter_Casing(string httpMethod, string requestUrl, string expectedActionName) { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl); // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -446,16 +446,16 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task LegacyActionSelection_RouteWithActionName(string httpMethod, string requestUrl, string expectedActionName) { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl); // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -473,16 +473,16 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task LegacyActionSelection_RouteWithActionName_Casing(string httpMethod, string requestUrl, string expectedActionName) { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl); // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -498,16 +498,16 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task LegacyActionSelection_RouteWithoutActionName(string httpMethod, string requestUrl, string expectedActionName) { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl); // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -526,16 +526,16 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task LegacyActionSelection_ModelBindingParameterAttribute_AreAppliedWhenSelectingActions(string httpMethod, string requestUrl, string expectedActionName) { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl); // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -550,16 +550,16 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task LegacyActionSelection_ActionsThatHaveSubsetOfRouteParameters_AreConsideredForSelection(string httpMethod, string requestUrl, string expectedActionName) { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl); // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -572,7 +572,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task LegacyActionSelection_RequestToAmbiguousAction_OnDefaultRoute() { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(new HttpMethod("POST"), "http://localhost/api/Admin/Test?name=mario"); @@ -589,16 +589,16 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public async Task LegacyActionSelection_SelectAction_ReturnsActionDescriptor_ForEnumParameterOverloads(string httpMethod, string requestUrl, string expectedActionName) { // Arrange - var server = TestServer.Create(_services, _app); + var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var request = new HttpRequestMessage(new HttpMethod(httpMethod), "http://localhost/" + requestUrl); // Act var response = await client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); + var data = Assert.Single(response.Headers.GetValues("ActionSelection")); + var result = JsonConvert.DeserializeObject(data); //Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); diff --git a/test/WebSites/WebApiCompatShimWebSite/ActionSelectionFilter.cs b/test/WebSites/WebApiCompatShimWebSite/ActionSelectionFilter.cs index 875794108b..484980760c 100644 --- a/test/WebSites/WebApiCompatShimWebSite/ActionSelectionFilter.cs +++ b/test/WebSites/WebApiCompatShimWebSite/ActionSelectionFilter.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Mvc; +using Newtonsoft.Json; namespace WebApiCompatShimWebSite { @@ -10,11 +11,18 @@ namespace WebApiCompatShimWebSite public override void OnActionExecuted(ActionExecutedContext context) { var action = (ControllerActionDescriptor)context.ActionDescriptor; - context.Result = new JsonResult(new - { - ActionName = action.Name, - ControllerName = action.ControllerName - }); + context.HttpContext.Response.Headers.Add( + "ActionSelection", + new string[] + { + JsonConvert.SerializeObject(new + { + ActionName = action.Name, + ControllerName = action.ControllerName + }) + }); + + context.Result = new HttpStatusCodeResult(200); } } } \ No newline at end of file From c6bc97afbfeb5c8e8cbd546fdecdca241639de96 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 10 Dec 2014 17:08:25 -0800 Subject: [PATCH 006/118] Update precompilation to use the library manager instead of relative paths. This website is written to assume that $pwd is always in the functional tests directory when it's launched. This works fine for the functional tests, but causes issues in many other contexts, including VS. --- .../Compiler/PreProcess/RazorPreCompilation.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/test/WebSites/PrecompilationWebSite/Compiler/PreProcess/RazorPreCompilation.cs b/test/WebSites/PrecompilationWebSite/Compiler/PreProcess/RazorPreCompilation.cs index d2080e4385..bd1d3d300b 100644 --- a/test/WebSites/PrecompilationWebSite/Compiler/PreProcess/RazorPreCompilation.cs +++ b/test/WebSites/PrecompilationWebSite/Compiler/PreProcess/RazorPreCompilation.cs @@ -25,16 +25,14 @@ namespace PrecompilationWebSite public static IServiceProvider ReplaceProvider(IServiceProvider provider) { var originalEnvironment = provider.GetService(); - var newPath = Path.GetFullPath( - Path.Combine( - originalEnvironment.ApplicationBasePath, - "..", - "WebSites", - "PrecompilationWebSite")); + + var libraryManager = provider.GetService(); + var info = libraryManager.GetLibraryInformation("PrecompilationWebSite"); + var directory = Path.GetDirectoryName(info.Path); var precompilationApplicationEnvironment = new PrecompilationApplicationEnvironment( originalEnvironment, - newPath); + directory); var collection = HostingServices.Create(provider); collection.AddInstance(precompilationApplicationEnvironment); From 09b01d8f3212f19fcb095ffbf1fe4701c69e47ec Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 11 Dec 2014 09:41:52 -0800 Subject: [PATCH 007/118] update comment about precompilation (cr feedback) --- .../Compiler/PreProcess/RazorPreCompilation.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/WebSites/PrecompilationWebSite/Compiler/PreProcess/RazorPreCompilation.cs b/test/WebSites/PrecompilationWebSite/Compiler/PreProcess/RazorPreCompilation.cs index bd1d3d300b..eafcca848b 100644 --- a/test/WebSites/PrecompilationWebSite/Compiler/PreProcess/RazorPreCompilation.cs +++ b/test/WebSites/PrecompilationWebSite/Compiler/PreProcess/RazorPreCompilation.cs @@ -18,10 +18,9 @@ namespace PrecompilationWebSite { } - // When running in memory tests the application base path will point to the functional tests - // project folder. - // We need to replace it to point to the actual precompilation website so that the views can - // be found. + // We need to construct an IApplicationEnvironment with a base path that that matches this + // website, in order to find the razor files. This is needed because we don't have a guarantee that + // the base path of the current app is this site (ex: functional tests). public static IServiceProvider ReplaceProvider(IServiceProvider provider) { var originalEnvironment = provider.GetService(); From 3dfcc884fe4eefb38182871caa76df90317d9db3 Mon Sep 17 00:00:00 2001 From: Praburaj Date: Tue, 9 Dec 2014 16:37:56 -0800 Subject: [PATCH 008/118] Changes to address IFileSystem.Watch method addition Absorbs the new IFileSystem interface. This change is to just address the breaking change introduced in IFileSystem. Razor has to do the necessary changes to subscribe to the Watch event for expiring the modified files. --- .../Compilation/DefaultRazorFileSystemCache.cs | 7 +++++++ test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs | 6 ------ .../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs | 5 +++++ .../Compilation/DefaultRazorFileSystemCacheTest.cs | 3 ++- 4 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileSystemCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileSystemCache.cs index 82dd51d9fd..b60d694078 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileSystemCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileSystemCache.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Concurrent; using Microsoft.AspNet.FileSystems; using Microsoft.Framework.OptionsModel; +using Microsoft.Framework.Expiration.Interfaces; namespace Microsoft.AspNet.Mvc.Razor { @@ -71,6 +72,12 @@ namespace Microsoft.AspNet.Mvc.Razor } } + /// + public IExpirationTrigger Watch(string filter) + { + return _fileSystem.Watch(filter); + } + private class ExpiringFileInfo { public IFileInfo FileInfo { get; set; } diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs index 6941503f81..21e36ecde4 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs @@ -5,7 +5,6 @@ using System; using System.IO; using System.Text; using Microsoft.AspNet.FileSystems; -using Microsoft.Framework.Expiration.Interfaces; namespace Microsoft.AspNet.Mvc.Razor { @@ -61,10 +60,5 @@ namespace Microsoft.AspNet.Mvc.Razor { throw new NotSupportedException(); } - - public IExpirationTrigger CreateFileChangeTrigger() - { - throw new NotSupportedException(); - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs index 3b7f2d1c7c..84dfc25b80 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using Microsoft.AspNet.FileSystems; +using Microsoft.Framework.Expiration.Interfaces; namespace Microsoft.AspNet.Mvc.Razor { @@ -53,5 +54,9 @@ namespace Microsoft.AspNet.Mvc.Razor } } + public IExpirationTrigger Watch(string filter) + { + throw new NotImplementedException(); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileSystemCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileSystemCacheTest.cs index f10ec52df3..3ff4f5ed29 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileSystemCacheTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileSystemCacheTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Microsoft.AspNet.FileSystems; using Microsoft.Framework.OptionsModel; +using Microsoft.Framework.Expiration.Interfaces; using Moq; using Xunit; @@ -404,7 +405,7 @@ namespace Microsoft.AspNet.Mvc.Razor } } - public bool TryGetParentPath(string subpath, out string parentPath) + public IExpirationTrigger Watch(string filter) { throw new NotImplementedException(); } From 3e26142de1c74db1ed1269db08099a44eb04e5c3 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 11 Dec 2014 15:51:15 -0800 Subject: [PATCH 009/118] Remove use of `Assert.DoesNotThrow()` - no longer in xunit :smiley: --- .../ActionResults/EmptyResultTests.cs | 4 +-- .../DefaultActionSelectorTests.cs | 8 ++++-- .../DefaultControllerFactoryTest.cs | 4 +-- .../Internal/MvcServicesHelperTests.cs | 4 +-- .../Internal/TaskHelperTest.cs | 4 +-- .../DependencyResolverTests.cs | 14 ++++------- .../DefaultBodyModelValidatorTests.cs | 25 ++++++------------- .../ReferenceEqualityComparerTest.cs | 7 ++++-- .../RazorPageTest.cs | 4 +-- .../OptionTagHelperTest.cs | 8 +++--- 10 files changed, 38 insertions(+), 44 deletions(-) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/EmptyResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/EmptyResultTests.cs index f6cb2d9f2f..0ab66ae52f 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/EmptyResultTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/EmptyResultTests.cs @@ -22,8 +22,8 @@ namespace Microsoft.AspNet.Mvc var context = new ActionContext(httpContext.Object, routeData, actionDescriptor); - // Act & Assert - Assert.DoesNotThrow(() => emptyResult.ExecuteResult(context)); + // Act & Assert (does not throw) + emptyResult.ExecuteResult(context); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs index cb709acaf7..c8b5b71b49 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs @@ -55,7 +55,9 @@ namespace Microsoft.AspNet.Mvc Assert.Empty(values.ActionsMatchingActionConstraints); Assert.Empty(values.FinalMatches); Assert.Null(values.SelectedAction); - Assert.DoesNotThrow(() => values.Summary); + + // (does not throw) + Assert.NotEmpty(values.Summary); } [Fact] @@ -146,7 +148,9 @@ namespace Microsoft.AspNet.Mvc Assert.Equal(actions, values.ActionsMatchingActionConstraints); Assert.Equal(actions, values.FinalMatches); Assert.Null(values.SelectedAction); - Assert.DoesNotThrow(() => values.Summary); + + // (does not throw) + Assert.NotEmpty(values.Summary); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerFactoryTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerFactoryTest.cs index 1cd7631771..6a40b3db9b 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerFactoryTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerFactoryTest.cs @@ -40,8 +40,8 @@ namespace Microsoft.AspNet.Mvc.Core.Test var controller = new Object(); - // Act + Assert - Assert.DoesNotThrow(() => factory.ReleaseController(controller)); + // Act + Assert (does not throw) + factory.ReleaseController(controller); } private class MyController : Controller diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Internal/MvcServicesHelperTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Internal/MvcServicesHelperTests.cs index fca8f1eb20..5637d35546 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Internal/MvcServicesHelperTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Internal/MvcServicesHelperTests.cs @@ -37,8 +37,8 @@ namespace Microsoft.AspNet.Mvc services.Setup(o => o.GetService(typeof(MvcMarkerService))) .Returns(expectedOutput); - // Act & Assert - Assert.DoesNotThrow(() => MvcServicesHelper.ThrowIfMvcNotRegistered(services.Object)); + // Act & Assert (does not throw) + MvcServicesHelper.ThrowIfMvcNotRegistered(services.Object); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Internal/TaskHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Internal/TaskHelperTest.cs index c764fddc94..346d3dd8e7 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Internal/TaskHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Internal/TaskHelperTest.cs @@ -15,8 +15,8 @@ namespace Microsoft.AspNet.Mvc.Internal // Arrange var task = Task.FromResult(0); - // Act and Assert - Assert.DoesNotThrow(() => TaskHelper.WaitAndThrowIfFaulted(task)); + // Act and Assert (does not throw) + TaskHelper.WaitAndThrowIfFaulted(task); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/DependencyResolverTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/DependencyResolverTests.cs index d23e036c24..6543101b8b 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/DependencyResolverTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/DependencyResolverTests.cs @@ -22,17 +22,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Arrange var provider = TestHelper.CreateServices("AutofacWebSite"); Action app = new Startup().Configure; - HttpResponseMessage response = null; - // Act & Assert - await Assert.DoesNotThrowAsync(async () => - { - // This essentially calls into the Startup.Configuration method - var server = TestServer.Create(provider, app); + // Act & Assert (does not throw) + // This essentially calls into the Startup.Configuration method + var server = TestServer.Create(provider, app); - // Make a request to start resolving DI pieces - response = await server.CreateClient().GetAsync(url); - }); + // Make a request to start resolving DI pieces + var response = await server.CreateClient().GetAsync(url); var actualResponseBody = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedResponseBody, actualResponseBody); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs index db9629e88d..6234649072 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/DefaultBodyModelValidatorTests.cs @@ -214,10 +214,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Arrange var validationContext = GetModelValidationContext(model, type); - // Act - Assert.DoesNotThrow(() => - new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty) - ); + // Act (does not throw) + new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty); // Assert var actualErrors = new Dictionary(); @@ -278,12 +276,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Arrange var validationContext = GetModelValidationContext(input, type, excludedTypes); - // Act & Assert - Assert.DoesNotThrow( - () => - { - new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty); - }); + // Act & Assert (does not throw) + new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty); Assert.True(validationContext.ModelState.IsValid); } @@ -312,10 +306,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var model = new Address() { Street = "Microsoft Way" }; var validationContext = GetModelValidationContext(model, model.GetType()); - // Act - Assert.DoesNotThrow(() => - new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty) - ); + // Act (does not throw) + new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty); // Assert Assert.Contains("Street", validationContext.ModelState.Keys); @@ -336,9 +328,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding }; var validationContext = GetModelValidationContext(instance, typeof(TypeThatOverridesEquals[])); - // Act & Assert - Assert.DoesNotThrow( - () => new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty)); + // Act & Assert (does not throw) + new DefaultBodyModelValidator().Validate(validationContext, keyPrefix: string.Empty); } private ModelValidationContext GetModelValidationContext( diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/ReferenceEqualityComparerTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/ReferenceEqualityComparerTest.cs index 3c89a16c65..bdd5398ca5 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/ReferenceEqualityComparerTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Validation/ReferenceEqualityComparerTest.cs @@ -30,7 +30,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var t1 = new TypeThatOverridesEquals(); var t2 = new TypeThatOverridesEquals(); - Assert.DoesNotThrow(() => ReferenceEqualityComparer.Instance.Equals(t1, t2)); + // Act & Assert (does not throw) + ReferenceEqualityComparer.Instance.Equals(t1, t2); } [Fact] @@ -58,7 +59,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding public void GetHashCode_DoesNotThrowForNull() { var comparer = ReferenceEqualityComparer.Instance; - Assert.DoesNotThrow(() => comparer.GetHashCode(null)); + + // Act & Assert (does not throw) + comparer.GetHashCode(null); } private class TypeThatOverridesEquals diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs index 48c77e8125..9a91842158 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorPageTest.cs @@ -522,9 +522,9 @@ namespace Microsoft.AspNet.Mvc.Razor await page.ExecuteAsync(); page.IsLayoutBeingRendered = true; - // Assert + // Assert (does not throw) var renderAsyncDelegate = page.SectionWriters["test-section"]; - await Assert.DoesNotThrowAsync(() => renderAsyncDelegate(TextWriter.Null)); + await renderAsyncDelegate(TextWriter.Null); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs index 24a06d3e8e..72d83f837f 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs @@ -208,9 +208,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers ViewContext = viewContext, }; - // Act & Assert + // Act & Assert (does not throw) // Tag helper would throw an NRE if it used Generator value. - await Assert.DoesNotThrowAsync(() => tagHelper.ProcessAsync(tagHelperContext, output)); + await tagHelper.ProcessAsync(tagHelperContext, output); } [Theory] @@ -247,9 +247,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Value = value, }; - // Act & Assert + // Act & Assert (does not throw) // Tag helper would throw an NRE if it used ViewContext or Generator values. - await Assert.DoesNotThrowAsync(() => tagHelper.ProcessAsync(tagHelperContext, output)); + await tagHelper.ProcessAsync(tagHelperContext, output); } } } From 263e75c8a6b4addeaae6b2729d78252627de3301 Mon Sep 17 00:00:00 2001 From: DamianEdwards Date: Fri, 12 Dec 2014 16:15:20 -0800 Subject: [PATCH 010/118] Update tests for Razor Tag Helpers Unique ID feature: - aspnet/razor#241 --- .../MvcRazorHostTest.cs | 68 ++++++++++++++++++- .../Runtime/ModelExpressionTagHelper.cs | 2 +- .../AnchorTagHelperTest.cs | 7 +- .../FormTagHelperTest.cs | 14 ++-- .../InputTagHelperTest.cs | 16 ++--- .../LabelTagHelperTest.cs | 4 +- .../OptionTagHelperTest.cs | 6 +- .../SelectTagHelperTest.cs | 14 ++-- .../TagHelperOutputExtensionsTest.cs | 6 +- .../TextAreaTagHelperTest.cs | 4 +- .../ValidationMessageTagHelperTest.cs | 3 +- .../ValidationSummaryTagHelperTest.cs | 2 +- 12 files changed, 109 insertions(+), 37 deletions(-) diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs index cac8962cb9..210b1ed6bf 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs @@ -3,8 +3,11 @@ using System.Collections.Generic; using System.IO; +using Microsoft.AspNet.FileSystems; using Microsoft.AspNet.Razor; +using Microsoft.AspNet.Razor.Generator; using Microsoft.AspNet.Razor.Generator.Compiler; +using Microsoft.AspNet.Razor.Generator.Compiler.CSharp; using Microsoft.AspNet.Razor.Text; using Xunit; @@ -66,7 +69,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void MvcRazorHost_ParsesAndGeneratesCodeForBasicScenarios(string scenarioName) { // Arrange - var host = new MvcRazorHost(new TestFileSystem()); + var host = new TestMvcRazorHost(new TestFileSystem()); // Act and Assert RunRuntimeTest(host, scenarioName); @@ -205,5 +208,68 @@ namespace Microsoft.AspNet.Mvc.Razor documentLocation: new MappingLocation(documentLocation, contentLength), generatedLocation: new MappingLocation(generatedLocation, contentLength)); } + + /// + /// Used when testing Tag Helpers, it disables the unique ID generation feature. + /// + private class TestMvcRazorHost : MvcRazorHost + { + public TestMvcRazorHost(IFileSystem fileSystem) + : base(fileSystem) + { } + + public override CodeBuilder DecorateCodeBuilder(CodeBuilder incomingBuilder, CodeBuilderContext context) + { + base.DecorateCodeBuilder(incomingBuilder, context); + + return new TestCSharpCodeBuilder(context, + DefaultModel, + ActivateAttribute, + new GeneratedTagHelperAttributeContext + { + ModelExpressionTypeName = ModelExpressionType, + CreateModelExpressionMethodName = CreateModelExpressionMethod + }); + } + + protected class TestCSharpCodeBuilder : MvcCSharpCodeBuilder + { + private readonly GeneratedTagHelperAttributeContext _tagHelperAttributeContext; + + public TestCSharpCodeBuilder(CodeBuilderContext context, + string defaultModel, + string activateAttribute, + GeneratedTagHelperAttributeContext tagHelperAttributeContext) + : base(context, defaultModel, activateAttribute, tagHelperAttributeContext) + { + _tagHelperAttributeContext = tagHelperAttributeContext; + } + + protected override CSharpCodeVisitor CreateCSharpCodeVisitor(CSharpCodeWriter writer, CodeBuilderContext context) + { + var visitor = base.CreateCSharpCodeVisitor(writer, context); + visitor.TagHelperRenderer = new NoUniqueIdsTagHelperCodeRenderer(visitor, writer, context) + { + AttributeValueCodeRenderer = + new MvcTagHelperAttributeValueCodeRenderer(_tagHelperAttributeContext) + }; + return visitor; + } + + private class NoUniqueIdsTagHelperCodeRenderer : CSharpTagHelperCodeRenderer + { + public NoUniqueIdsTagHelperCodeRenderer(IChunkVisitor bodyVisitor, + CSharpCodeWriter writer, + CodeBuilderContext context) + : base(bodyVisitor, writer, context) + { } + + protected override string GenerateUniqueId() + { + return "test"; + } + } + } + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs index 581614783e..1bcafcc482 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs @@ -43,7 +43,7 @@ namespace Asp BeginContext(120, 2, true); WriteLiteral("\r\n"); EndContext(); - __tagHelperExecutionContext = __tagHelperScopeManager.Begin("inputTest"); + __tagHelperExecutionContext = __tagHelperScopeManager.Begin("inputTest", "test"); __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper = CreateTagHelper(); __tagHelperExecutionContext.Add(__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper); __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For = CreateModelExpression(__model => __model.Now); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs index 92ed100dcb..56427a00b7 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs @@ -31,7 +31,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "asp-fragment", "hello=world" }, { "asp-host", "contoso.com" }, { "asp-protocol", "http" } - }); + }, + uniqueId: "test"); var output = new TagHelperOutput( expectedTagName, attributes: new Dictionary @@ -84,7 +85,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { // Arrange var context = new TagHelperContext( - allAttributes: new Dictionary()); + allAttributes: new Dictionary(), uniqueId: "test"); var output = new TagHelperOutput( "a", attributes: new Dictionary(), @@ -118,7 +119,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { // Arrange var context = new TagHelperContext( - allAttributes: new Dictionary()); + allAttributes: new Dictionary(), uniqueId: "test"); var output = new TagHelperOutput( "a", attributes: new Dictionary(), diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs index 82858c9991..9b604ff1f8 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs @@ -32,7 +32,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "asp-controller", "home" }, { "method", "post" }, { "asp-anti-forgery", true } - }); + }, + uniqueId: "test"); var output = new TagHelperOutput( expectedTagName, attributes: new Dictionary @@ -91,7 +92,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var viewContext = CreateViewContext(); var context = new TagHelperContext( - allAttributes: new Dictionary()); + allAttributes: new Dictionary(), uniqueId: "test"); var output = new TagHelperOutput( "form", attributes: new Dictionary(), @@ -132,7 +133,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var testViewContext = CreateViewContext(); var context = new TagHelperContext( - allAttributes: new Dictionary()); + allAttributes: new Dictionary(), uniqueId: "test"); var expectedAttribute = new KeyValuePair("asp-ROUTEE-NotRoute", "something"); var output = new TagHelperOutput( "form", @@ -193,7 +194,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var viewContext = CreateViewContext(); var context = new TagHelperContext( - allAttributes: new Dictionary()); + allAttributes: new Dictionary(), uniqueId: "test"); var output = new TagHelperOutput( "form", attributes: new Dictionary(), @@ -243,7 +244,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers allAttributes: new Dictionary() { { "METhod", "POST" } - }); + }, + uniqueId: "test"); // Act await formTagHelper.ProcessAsync(context, output); @@ -284,7 +286,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "aCTiON", "my-action" }, }, content: string.Empty); - var context = new TagHelperContext(allAttributes: new Dictionary()); + var context = new TagHelperContext(allAttributes: new Dictionary(), uniqueId: "test"); // Act await formTagHelper.ProcessAsync(context, output); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs index 4124a5a7cd..ac62d6a1e7 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs @@ -92,7 +92,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedContent = "original content"; var expectedTagName = "not-input"; - var context = new TagHelperContext(new Dictionary()); + var context = new TagHelperContext(new Dictionary(), uniqueId: "test"); var originalAttributes = new Dictionary { { "class", "form-control" }, @@ -137,7 +137,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var originalTagName = "not-input"; var expectedContent = originalContent + ""; - var context = new TagHelperContext(allAttributes: new Dictionary()); + var context = new TagHelperContext(allAttributes: new Dictionary(), uniqueId: "test"); var originalAttributes = new Dictionary { { "class", "form-control" }, @@ -217,7 +217,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedContent = "something"; var expectedTagName = "not-input"; - var context = new TagHelperContext(allAttributes: contextAttributes); + var context = new TagHelperContext(allAttributes: contextAttributes, uniqueId: "test"); var originalAttributes = new Dictionary { { "class", "form-control" }, @@ -294,7 +294,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedContent = "something"; var expectedTagName = "not-input"; - var context = new TagHelperContext(allAttributes: contextAttributes); + var context = new TagHelperContext(allAttributes: contextAttributes, uniqueId: "test"); var originalAttributes = new Dictionary { { "class", "form-control" }, @@ -368,7 +368,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedContent = "something"; var expectedTagName = "not-input"; - var context = new TagHelperContext(allAttributes: contextAttributes); + var context = new TagHelperContext(allAttributes: contextAttributes, uniqueId: "test"); var originalAttributes = new Dictionary { { "class", "form-control" }, @@ -457,7 +457,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedContent = "something"; var expectedTagName = "not-input"; - var context = new TagHelperContext(allAttributes: contextAttributes); + var context = new TagHelperContext(allAttributes: contextAttributes, uniqueId: "test"); var originalAttributes = new Dictionary { { "class", "form-control" }, @@ -515,7 +515,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedContent = "original content"; var expectedTagName = "input"; - var tagHelperContext = new TagHelperContext(new Dictionary()); + var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); var output = new TagHelperOutput(expectedTagName, expectedAttributes, expectedContent) { SelfClosing = false, @@ -563,7 +563,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedMessage = "Unable to format without a 'asp-for' expression for . 'asp-format' must " + "be null if 'asp-for' is null."; - var tagHelperContext = new TagHelperContext(contextAttributes); + var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); var output = new TagHelperOutput(expectedTagName, originalAttributes, content); var tagHelper = new InputTagHelper { diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs index 892b793511..ebce192391 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs @@ -113,7 +113,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers For = modelExpression, }; - var tagHelperContext = new TagHelperContext(allAttributes: new Dictionary()); + var tagHelperContext = new TagHelperContext(allAttributes: new Dictionary(), uniqueId: "test"); var htmlAttributes = new Dictionary { { "class", "form-control" }, @@ -153,7 +153,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var modelExpression = new ModelExpression(nameof(Model.Text), metadata); var tagHelper = new LabelTagHelper(); - var tagHelperContext = new TagHelperContext(allAttributes: new Dictionary()); + var tagHelperContext = new TagHelperContext(allAttributes: new Dictionary(), uniqueId: "test"); var output = new TagHelperOutput(expectedTagName, expectedAttributes, expectedContent); var htmlGenerator = new TestableHtmlGenerator(metadataProvider); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs index 72d83f837f..24ceea9704 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs @@ -136,7 +136,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "selected", selected }, { "value", value }, }; - var tagHelperContext = new TagHelperContext(contextAttributes); + var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); var output = new TagHelperOutput(expectedTagName, originalAttributes, originalContent) { SelfClosing = false, @@ -188,7 +188,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "selected", selected }, { "value", value }, }; - var tagHelperContext = new TagHelperContext(contextAttributes); + var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); var output = new TagHelperOutput(originalTagName, originalAttributes, originalContent) { SelfClosing = false, @@ -235,7 +235,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "selected", selected }, { "value", value }, }; - var tagHelperContext = new TagHelperContext(contextAttributes); + var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); var output = new TagHelperOutput(originalTagName, originalAttributes, originalContent) { SelfClosing = false, diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs index b19e01f6b0..9793d7c088 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs @@ -182,7 +182,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var metadata = metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName: "Text"); var modelExpression = new ModelExpression(nameAndId.Name, metadata); - var tagHelperContext = new TagHelperContext(new Dictionary()); + var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); var output = new TagHelperOutput(expectedTagName, originalAttributes, expectedContent) { SelfClosing = true, @@ -252,7 +252,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var metadata = metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName: "Text"); var modelExpression = new ModelExpression(nameAndId.Name, metadata); - var tagHelperContext = new TagHelperContext(new Dictionary()); + var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); var output = new TagHelperOutput(expectedTagName, originalAttributes, originalContent) { SelfClosing = true, @@ -312,7 +312,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var propertyName = "Property1"; var expectedTagName = "select"; - var tagHelperContext = new TagHelperContext(contextAttributes); + var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); var output = new TagHelperOutput(expectedTagName, originalAttributes, content); // TODO: https://github.com/aspnet/Mvc/issues/1253 @@ -376,7 +376,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var propertyName = "Property1"; var tagName = "select"; - var tagHelperContext = new TagHelperContext(contextAttributes); + var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); var output = new TagHelperOutput(tagName, originalAttributes, content); var metadataProvider = new EmptyModelMetadataProvider(); @@ -440,7 +440,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedContent = "original content"; var expectedTagName = "select"; - var tagHelperContext = new TagHelperContext(contextAttributes); + var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); var output = new TagHelperOutput(expectedTagName, originalAttributes, expectedContent) { SelfClosing = true, @@ -471,7 +471,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var expectedTagName = "select"; var expectedMessage = "Cannot determine body for . Acceptable values are 'false', 'true' and 'multiple'."; - var tagHelperContext = new TagHelperContext(contextAttributes); + var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); var output = new TagHelperOutput(expectedTagName, originalAttributes, content); var metadataProvider = new EmptyModelMetadataProvider(); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs index 49dd211966..ba0a980c95 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs @@ -25,7 +25,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers new Dictionary(StringComparer.Ordinal) { { attributeName, attributeValue } - }); + }, + uniqueId: "test"); var expectedAttribute = new KeyValuePair(attributeName, attributeValue); // Act @@ -53,7 +54,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers new Dictionary(StringComparer.Ordinal) { { attributeName, "world" } - }); + } + , uniqueId: "test"); // Act tagHelperOutput.CopyHtmlAttribute(attributeName, tagHelperContext); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs index 74956ea0d6..1b098e9e04 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs @@ -107,7 +107,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers For = modelExpression, }; - var tagHelperContext = new TagHelperContext(new Dictionary()); + var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); var htmlAttributes = new Dictionary { { "class", "form-control" }, @@ -157,7 +157,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var modelExpression = new ModelExpression(nameof(Model.Text), metadata); var tagHelper = new TextAreaTagHelper(); - var tagHelperContext = new TagHelperContext(new Dictionary()); + var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); var output = new TagHelperOutput(expectedTagName, expectedAttributes, expectedContent) { SelfClosing = true, diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs index b0290add45..e7a22d90ab 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs @@ -33,7 +33,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { { "id", "myvalidationmessage" }, { "for", modelExpression }, - }); + }, + uniqueId: "test"); var output = new TagHelperOutput( expectedTagName, attributes: new Dictionary diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs index 8f5e06cfcd..bfbd4f6e6e 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers ValidationSummaryValue = "All" }; - var tagHelperContext = new TagHelperContext(new Dictionary()); + var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); var output = new TagHelperOutput( expectedTagName, attributes: new Dictionary From 1a617eb5335bdba8631094fad74e528d73ecd13b Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 10 Dec 2014 15:54:06 -0800 Subject: [PATCH 011/118] Fix an inadvertent 204 in activator tests On a web server, this test ends up giving back a 204 because of the MVC behavior when an action is declared to return void. The fix is to use EmptyResult. --- .../ActivatorWebSite/Controllers/RegularController.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/WebSites/ActivatorWebSite/Controllers/RegularController.cs b/test/WebSites/ActivatorWebSite/Controllers/RegularController.cs index 208a549588..90bd1a80d6 100644 --- a/test/WebSites/ActivatorWebSite/Controllers/RegularController.cs +++ b/test/WebSites/ActivatorWebSite/Controllers/RegularController.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc; @@ -8,13 +9,15 @@ namespace ActivatorWebSite { public class RegularController : Controller { - public void Index() + public async Task Index() { // This verifies that ModelState and Context are activated. if (ModelState.IsValid) { - Context.Response.WriteAsync("Hello world").Wait(); + await Context.Response.WriteAsync("Hello world"); } + + return new EmptyResult(); } } } \ No newline at end of file From 6390bad0d3fbac3be7c623c2650a033acb7cfd6a Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 10 Dec 2014 17:46:18 -0800 Subject: [PATCH 012/118] Adding a pattern for returning 'unhandled' exception information via middleware. This should be used where posssible instead of throwing an exception and catching in a test, as that only works in memory. --- .../ActivatorTests.cs | 16 +++-- .../AntiForgeryTests.cs | 45 ++++++++---- .../ExceptionInfo.cs | 16 +++++ .../FiltersTest.cs | 34 ++++++--- .../HttpResponseMessageExceptions.cs | 48 +++++++++++++ .../InlineConstraintTests.cs | 14 ++-- .../ModelBindingTests.cs | 69 ++++++++++++------- .../RoutingTests.cs | 6 +- .../WebApiCompatShimActionSelectionTest.cs | 8 ++- .../XmlSerializerInputFormatterTests.cs | 9 ++- test/WebSites/ActivatorWebSite/Startup.cs | 3 + test/WebSites/AntiForgeryWebSite/Startup.cs | 2 + test/WebSites/FiltersWebSite/Startup.cs | 2 + .../InlineConstraintsWebSite/Startup.cs | 5 +- .../BuilderExtensions.cs | 6 ++ .../ErrorReporterMiddleware.cs | 51 ++++++++++++++ test/WebSites/ModelBindingWebSite/Startup.cs | 2 + test/WebSites/RoutingWebSite/Startup.cs | 2 + .../WebApiCompatShimWebSite/Startup.cs | 2 + test/WebSites/XmlSerializerWebSite/Startup.cs | 2 + 20 files changed, 268 insertions(+), 74 deletions(-) create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/ExceptionInfo.cs create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/HttpResponseMessageExceptions.cs create mode 100644 test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/ErrorReporterMiddleware.cs diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActivatorTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActivatorTests.cs index 4d23790d48..fd3e27da20 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActivatorTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActivatorTests.cs @@ -21,12 +21,16 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Arrange var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); + var expectedMessage = "No service for type 'ActivatorWebSite.CannotBeActivatedController+FakeType' " + "has been registered."; // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.GetAsync("http://localhost/CannotBeActivated/Index")); - Assert.Equal(expectedMessage, ex.Message); + var response = await client.GetAsync("http://localhost/CannotBeActivated/Index"); + + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal(expectedMessage, exception.ExceptionMessage); } [Fact] @@ -162,9 +166,11 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests "has been registered."; // Act & Assert - var ex = await Assert.ThrowsAsync( - () => client.GetAsync("http://localhost/View/ConsumeCannotBeActivatedComponent")); - Assert.Equal(expectedMessage, ex.Message); + var response = await client.GetAsync("http://localhost/View/ConsumeCannotBeActivatedComponent"); + + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal(expectedMessage, exception.ExceptionMessage); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs index 806a1575c2..4a19cbe97a 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs @@ -100,9 +100,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests request.Content = new FormUrlEncodedContent(nameValueCollection); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); - Assert.Equal("The anti-forgery token could not be decrypted.", ex.Message); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); + Assert.Equal("The anti-forgery token could not be decrypted.", exception.ExceptionMessage); } [Fact] @@ -127,9 +130,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests request.Content = new FormUrlEncodedContent(nameValueCollection); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); - Assert.Equal("The anti-forgery token could not be decrypted.", ex.Message); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); + Assert.Equal("The anti-forgery token could not be decrypted.", exception.ExceptionMessage); } [Fact] @@ -162,9 +168,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests request.Content = new FormUrlEncodedContent(nameValueCollection); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); - Assert.Equal("The anti-forgery cookie token and form field token do not match.", ex.Message); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); + Assert.Equal("The anti-forgery cookie token and form field token do not match.", exception.ExceptionMessage); } [Fact] @@ -189,9 +198,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests request.Content = new FormUrlEncodedContent(nameValueCollection); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); - Assert.Equal("The required anti-forgery cookie \"__RequestVerificationToken\" is not present.", ex.Message); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); + Assert.Equal("The required anti-forgery cookie \"__RequestVerificationToken\" is not present.", exception.ExceptionMessage); } [Fact] @@ -214,10 +226,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests request.Content = new FormUrlEncodedContent(nameValueCollection); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => client.SendAsync(request)); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); Assert.Equal("The required anti-forgery form field \"__RequestVerificationToken\" is not present.", - ex.Message); + exception.ExceptionMessage); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ExceptionInfo.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ExceptionInfo.cs new file mode 100644 index 0000000000..1542984292 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ExceptionInfo.cs @@ -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. + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + /// + /// Information about an exception that occured on the server side of a functional + /// test. + /// + public class ExceptionInfo + { + public string ExceptionMessage { get; set; } + + public string ExceptionType { get; set; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs index bf4ba11293..b25743957e 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs @@ -124,8 +124,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var url = "http://localhost/RandomNumber/GetAuthorizedRandomNumber"; - // Act & Assert - await Assert.ThrowsAsync(() => client.GetAsync(url)); + // Act + var response = await client.GetAsync(url); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); } [Fact] @@ -152,8 +156,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); var url = "http://localhost/RandomNumber/GetHalfOfModifiedRandomNumber?randomNumber=3"; - // Act & Assert - await Assert.ThrowsAsync(() => client.GetAsync(url)); + // Act + var response = await client.GetAsync(url); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); } [Fact] @@ -465,9 +473,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - await Assert.ThrowsAsync( - () => client.GetAsync("http://localhost/Home/ThrowingResultFilter")); + // Act + var response = await client.GetAsync("http://localhost/Home/ThrowingResultFilter"); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidProgramException).FullName, exception.ExceptionType); } // Action Filter throws. @@ -496,9 +507,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - await Assert.ThrowsAsync( - () => client.GetAsync("http://localhost/Home/ThrowingAuthorizationFilter")); + // Act + var response = await client.GetAsync("http://localhost/Home/ThrowingAuthorizationFilter"); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidProgramException).FullName, exception.ExceptionType); } // Exception Filter throws. diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/HttpResponseMessageExceptions.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/HttpResponseMessageExceptions.cs new file mode 100644 index 0000000000..f2d87daba5 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/HttpResponseMessageExceptions.cs @@ -0,0 +1,48 @@ +// 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.Net; +using System.Net.Http; +using Microsoft.AspNet.Mvc.TestConfiguration; +using Xunit; +using Xunit.Sdk; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public static class HttpResponseMessageExceptions + { + public static ExceptionInfo GetServerException(this HttpResponseMessage response) + { + if (response.StatusCode != HttpStatusCode.InternalServerError) + { + throw new AssertActualExpectedException( + HttpStatusCode.InternalServerError, + response.StatusCode, + "A server-side exception should be returned as a 500."); + } + + var headers = response.Headers; + + IEnumerable exceptionMessageHeader; + IEnumerable exceptionTypeHeader; + if (!headers.TryGetValues(ErrorReporterMiddleware.ExceptionMessageHeader, out exceptionMessageHeader)) + { + throw new XunitException( + "No value for the '" + ErrorReporterMiddleware.ExceptionMessageHeader + "' header."); + } + + if (!headers.TryGetValues(ErrorReporterMiddleware.ExceptionTypeHeader, out exceptionTypeHeader)) + { + throw new XunitException( + "No value for the '" + ErrorReporterMiddleware.ExceptionTypeHeader + "' header."); + } + + return new ExceptionInfo() + { + ExceptionMessage = Assert.Single(exceptionMessageHeader), + ExceptionType = Assert.Single(exceptionTypeHeader), + }; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs index 5dc8d1961f..1f95804482 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/InlineConstraintTests.cs @@ -42,15 +42,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); - // Act & Assert - var ex = await Assert.ThrowsAsync( - () => client.GetAsync("http://localhost/area-withoutexists/Users") - ); + // Act + var response = await client.GetAsync("http://localhost/area-withoutexists/Users"); + // Assert + var exception = response.GetServerException(); Assert.Equal("The view 'Index' was not found." + - " The following locations were searched:\r\n/Areas/Users/Views/Home/Index.cshtml\r\n" + - "/Areas/Users/Views/Shared/Index.cshtml\r\n/Views/Shared/Index.cshtml.", - ex.Message); + " The following locations were searched:__/Areas/Users/Views/Home/Index.cshtml__" + + "/Areas/Users/Views/Shared/Index.cshtml__/Views/Shared/Index.cshtml.", + exception.ExceptionMessage); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs index 0fefaa5a9f..952d278ad1 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs @@ -128,12 +128,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => - client.GetAsync("http://localhost/FromAttributes/FromBodyParametersThrows")); + // Act + var response = await client.GetAsync("http://localhost/FromAttributes/FromBodyParametersThrows"); - Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.", - ex.Message); + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal( + "More than one parameter and/or property is bound to the HTTP request's content.", + exception.ExceptionMessage); } [Fact] @@ -143,12 +146,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => - client.GetAsync("http://localhost/FromAttributes/FromBodyParameterAndPropertyThrows")); + // Act + var response = await client.GetAsync("http://localhost/FromAttributes/FromBodyParameterAndPropertyThrows"); - Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.", - ex.Message); + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal( + "More than one parameter and/or property is bound to the HTTP request's content.", + exception.ExceptionMessage); } [Fact] @@ -158,12 +164,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => - client.GetAsync("http://localhost/FromAttributes/FormAndBody_AsParameters_Throws")); + // Act + var response = await client.GetAsync("http://localhost/FromAttributes/FormAndBody_AsParameters_Throws"); - Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.", - ex.Message); + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal( + "More than one parameter and/or property is bound to the HTTP request's content.", + exception.ExceptionMessage); } [Fact] @@ -173,12 +182,15 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var server = TestServer.Create(_services, _app); var client = server.CreateClient(); - // Act & Assert - var ex = await Assert.ThrowsAsync(() => - client.GetAsync("http://localhost/FromAttributes/FormAndBody_Throws")); + // Act + var response = await client.GetAsync("http://localhost/FromAttributes/FormAndBody_Throws"); - Assert.Equal("More than one parameter and/or property is bound to the HTTP request's content.", - ex.Message); + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal( + "More than one parameter and/or property is bound to the HTTP request's content.", + exception.ExceptionMessage); } [Fact] @@ -930,13 +942,18 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); Expression> expression = model => model.Address.Country; + var expected = string.Format( + "The passed expression of expression node type '{0}' is invalid." + + " Only simple member access expressions for model properties are supported.", + expression.Body.NodeType); + // Act - var ex = await Assert.ThrowsAsync(() => - client.GetAsync("http://localhost/TryUpdateModel/GetUserAsync_WithChainedProperties?id=123")); - Assert.Equal(string.Format("The passed expression of expression node type '{0}' is invalid." + - " Only simple member access expressions for model properties are supported.", - expression.Body.NodeType), - ex.Message); + var response = await client.GetAsync("http://localhost/TryUpdateModel/GetUserAsync_WithChainedProperties?id=123"); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); + Assert.Equal(expected, exception.ExceptionMessage); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs index 27182925da..623e961b3d 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs @@ -925,11 +925,11 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var expectedMessage = "The supplied route name 'DuplicateRoute' is ambiguous and matched more than one route."; // Act - var ex = await Assert.ThrowsAsync(async () => - await client.GetAsync(url)); + var response = await client.GetAsync(url); // Assert - Assert.Equal(expectedMessage, ex.Message); + var exception = response.GetServerException(); + Assert.Equal(expectedMessage, exception.ExceptionMessage); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs index 4731677348..a0ea027f79 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs @@ -577,8 +577,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var request = new HttpRequestMessage(new HttpMethod("POST"), "http://localhost/api/Admin/Test?name=mario"); - // Act & Assert - await Assert.ThrowsAsync(async () => await client.SendAsync(request)); + // Act + var response = await client.SendAsync(request); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(AmbiguousActionException).FullName, exception.ExceptionType); } [Theory] diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs index 7d2840796d..d4d48c6177 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs @@ -50,9 +50,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests "" + sampleInputInt.ToString() + ""; var content = new StringContent(input, Encoding.UTF8, "application/xml"); - // Act & Assert - await Assert.ThrowsAsync( - async () => await client.PostAsync("http://localhost/Home/Index", content)); + // Act + var response = await client.PostAsync("http://localhost/Home/Index", content); + + // Assert + var exception = response.GetServerException(); + Assert.Equal(typeof(InvalidOperationException).FullName, exception.ExceptionType); } } } \ No newline at end of file diff --git a/test/WebSites/ActivatorWebSite/Startup.cs b/test/WebSites/ActivatorWebSite/Startup.cs index dfc3a68168..c48637d7f9 100644 --- a/test/WebSites/ActivatorWebSite/Startup.cs +++ b/test/WebSites/ActivatorWebSite/Startup.cs @@ -22,6 +22,9 @@ namespace ActivatorWebSite services.AddScoped(); }); + // Used to report exceptions that MVC doesn't handle + app.UseErrorReporter(); + // Add MVC to the request pipeline app.UseMvc(routes => { diff --git a/test/WebSites/AntiForgeryWebSite/Startup.cs b/test/WebSites/AntiForgeryWebSite/Startup.cs index f54fd9b107..508c1e4378 100644 --- a/test/WebSites/AntiForgeryWebSite/Startup.cs +++ b/test/WebSites/AntiForgeryWebSite/Startup.cs @@ -17,6 +17,8 @@ namespace AntiForgeryWebSite services.AddMvc(configuration); }); + app.UseErrorReporter(); + app.UseMvc(routes => { routes.MapRoute("ActionAsMethod", "{controller}/{action}", diff --git a/test/WebSites/FiltersWebSite/Startup.cs b/test/WebSites/FiltersWebSite/Startup.cs index e546ed9162..6449d67012 100644 --- a/test/WebSites/FiltersWebSite/Startup.cs +++ b/test/WebSites/FiltersWebSite/Startup.cs @@ -28,6 +28,8 @@ namespace FiltersWebSite }); }); + app.UseErrorReporter(); + app.UseMvc(); } } diff --git a/test/WebSites/InlineConstraintsWebSite/Startup.cs b/test/WebSites/InlineConstraintsWebSite/Startup.cs index 9f455c0d28..698deb6c3c 100644 --- a/test/WebSites/InlineConstraintsWebSite/Startup.cs +++ b/test/WebSites/InlineConstraintsWebSite/Startup.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; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Constraints; @@ -11,8 +10,6 @@ namespace InlineConstraints { public class Startup { - public Action RouteCollectionProvider { get; set; } - public void Configure(IApplicationBuilder app) { var configuration = app.GetTestConfiguration(); @@ -22,6 +19,8 @@ namespace InlineConstraints services.AddMvc(configuration); }); + app.UseErrorReporter(); + app.UseMvc(routes => { routes.MapRoute("StoreId", diff --git a/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/BuilderExtensions.cs b/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/BuilderExtensions.cs index 8813249a29..2b253d444c 100644 --- a/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/BuilderExtensions.cs +++ b/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/BuilderExtensions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Mvc; +using Microsoft.AspNet.Mvc.TestConfiguration; using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; @@ -18,5 +19,10 @@ namespace Microsoft.AspNet.Builder return configuration; } + + public static IApplicationBuilder UseErrorReporter(this IApplicationBuilder app) + { + return app.Use(next => new ErrorReporterMiddleware(next).Invoke); + } } } \ No newline at end of file diff --git a/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/ErrorReporterMiddleware.cs b/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/ErrorReporterMiddleware.cs new file mode 100644 index 0000000000..fecc5835d6 --- /dev/null +++ b/test/WebSites/Microsoft.AspNet.Mvc.TestConfiguration/ErrorReporterMiddleware.cs @@ -0,0 +1,51 @@ +// 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.Builder; +using Microsoft.AspNet.Http; + +namespace Microsoft.AspNet.Mvc.TestConfiguration +{ + /// + /// A middleware that reports errors via header values. Useful for tests that want to verify + /// an exception that goes unhandled by the MVC part of the stack. + /// + public class ErrorReporterMiddleware + { + public static readonly string ExceptionMessageHeader = "ExceptionMessage"; + public static readonly string ExceptionTypeHeader = "ExceptionType"; + + private readonly RequestDelegate _next; + + public ErrorReporterMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext context) + { + try + { + await _next(context); + } + catch (Exception exception) + { + if (context.Response.HeadersSent) + { + throw; + } + else + { + context.Response.StatusCode = 500; + + var escapedMessage = exception.Message.Replace('\r', '_').Replace('\n', '_'); + + context.Response.Headers.Add(ExceptionTypeHeader, new string[] { exception.GetType().FullName }); + context.Response.Headers.Add(ExceptionMessageHeader, new string[] { escapedMessage }); + } + } + } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Startup.cs b/test/WebSites/ModelBindingWebSite/Startup.cs index ead23b1274..b3c8067032 100644 --- a/test/WebSites/ModelBindingWebSite/Startup.cs +++ b/test/WebSites/ModelBindingWebSite/Startup.cs @@ -30,6 +30,8 @@ namespace ModelBindingWebSite services.AddSingleton(); }); + app.UseErrorReporter(); + // Add MVC to the request pipeline app.UseMvc(routes => { diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index c353939d84..099b773f8b 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -20,6 +20,8 @@ namespace RoutingWebSite services.AddScoped(); }); + app.UseErrorReporter(); + app.UseMvc(routes => { routes.MapRoute("areaRoute", diff --git a/test/WebSites/WebApiCompatShimWebSite/Startup.cs b/test/WebSites/WebApiCompatShimWebSite/Startup.cs index 7b912327ec..485e8d9c18 100644 --- a/test/WebSites/WebApiCompatShimWebSite/Startup.cs +++ b/test/WebSites/WebApiCompatShimWebSite/Startup.cs @@ -19,6 +19,8 @@ namespace WebApiCompatShimWebSite services.AddWebApiConventions(); }); + app.UseErrorReporter(); + app.UseMvc(routes => { // This route can't access any of our webapi controllers diff --git a/test/WebSites/XmlSerializerWebSite/Startup.cs b/test/WebSites/XmlSerializerWebSite/Startup.cs index 6a044444d4..bd63f6cc0d 100644 --- a/test/WebSites/XmlSerializerWebSite/Startup.cs +++ b/test/WebSites/XmlSerializerWebSite/Startup.cs @@ -27,6 +27,8 @@ namespace XmlSerializerWebSite }); }); + app.UseErrorReporter(); + // Add MVC to the request pipeline app.UseMvc(routes => { From d3344f0766146ce0bb9ee81216f1c5058767a6ce Mon Sep 17 00:00:00 2001 From: Saar Cohen Date: Wed, 10 Dec 2014 16:33:01 +0000 Subject: [PATCH 013/118] Changed hard coded argument string in the throw to use nameof --- src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs index e7ae87b916..e634dcbfe6 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc throw new ArgumentException( Resources.FormatActionDescriptorMustBeBasedOnControllerAction( typeof(ControllerActionDescriptor)), - "actionContext"); + nameof(actionContext)); } var controller = _typeActivator.CreateInstance( From e0780764087eb49156b365ca51f6c5ba4a9c25b7 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 24 Nov 2014 14:01:16 -0800 Subject: [PATCH 014/118] Adding functional test for RazorViewEngineOptions.FileSystem --- Mvc.sln | 15 ++++++ .../Compilation/RoslynCompilationService.cs | 8 ++- .../RazorViewEngineOptionsTest.cs | 49 +++++++++++++++++++ .../project.json | 1 + .../RazorViewEngineOptions_AdminController.cs | 16 ++++++ .../RazorViewEngineOptions_HomeController.cs | 15 ++++++ .../RazorViewEngineOptions_Admin/Login.cshtml | 1 + .../RazorViewEngineOptions_Home/Index.cshtml | 1 + .../RazorViewEngineOptionsWebsite.kproj | 20 ++++++++ .../RazorViewEngineOptionsWebsite/Startup.cs | 39 +++++++++++++++ .../project.json | 20 ++++++++ .../wwwroot/readme.md | 1 + 12 files changed, 184 insertions(+), 2 deletions(-) create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewEngineOptionsTest.cs create mode 100644 test/WebSites/RazorViewEngineOptionsWebsite/Controllers/RazorViewEngineOptions_AdminController.cs create mode 100644 test/WebSites/RazorViewEngineOptionsWebsite/Controllers/RazorViewEngineOptions_HomeController.cs create mode 100644 test/WebSites/RazorViewEngineOptionsWebsite/EmbeddedResources/Areas/Restricted/Views/RazorViewEngineOptions_Admin/Login.cshtml create mode 100644 test/WebSites/RazorViewEngineOptionsWebsite/EmbeddedResources/Views/RazorViewEngineOptions_Home/Index.cshtml create mode 100644 test/WebSites/RazorViewEngineOptionsWebsite/RazorViewEngineOptionsWebsite.kproj create mode 100644 test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs create mode 100644 test/WebSites/RazorViewEngineOptionsWebsite/project.json create mode 100644 test/WebSites/RazorViewEngineOptionsWebsite/wwwroot/readme.md diff --git a/Mvc.sln b/Mvc.sln index 82092028de..f1bc715fb6 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -106,6 +106,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "PrecompilationWebSite", "te EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RequestServicesWebSite", "test\WebSites\RequestServicesWebSite\RequestServicesWebSite.kproj", "{F12E9CF0-4958-40C6-A6CD-759185157F84}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "RazorViewEngineOptionsWebsite", "test\WebSites\RazorViewEngineOptionsWebsite\RazorViewEngineOptionsWebsite.kproj", "{B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}" +EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CompositeViewEngineWebSite", "test\WebSites\CompositeViewEngineWebSite\CompositeViewEngineWebSite.kproj", "{A853B2BA-4449-4908-A416-5A3C027FC22B}" EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ValueProvidersWebSite", "test\WebSites\ValueProvidersWebSite\ValueProvidersWebSite.kproj", "{14F79E79-AE79-48FA-95DE-D794EF4EABB3}" @@ -570,6 +572,18 @@ Global {F12E9CF0-4958-40C6-A6CD-759185157F84}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {F12E9CF0-4958-40C6-A6CD-759185157F84}.Release|Mixed Platforms.Build.0 = Release|Any CPU {F12E9CF0-4958-40C6-A6CD-759185157F84}.Release|x86.ActiveCfg = Release|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|x86.ActiveCfg = Debug|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Debug|x86.Build.0 = Debug|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|Any CPU.Build.0 = Release|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|x86.ActiveCfg = Release|Any CPU + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D}.Release|x86.Build.0 = Release|Any CPU {A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Any CPU.Build.0 = Debug|Any CPU {A853B2BA-4449-4908-A416-5A3C027FC22B}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -645,6 +659,7 @@ Global {860119ED-3DB1-424D-8D0A-30132A8A7D96} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {59E1BE90-92C1-4D35-ADCC-B69F49077C81} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {F12E9CF0-4958-40C6-A6CD-759185157F84} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {B18ADE62-35DE-4A06-8E1D-EDD6245F7F4D} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {A853B2BA-4449-4908-A416-5A3C027FC22B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {14F79E79-AE79-48FA-95DE-D794EF4EABB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} EndGlobalSection diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs index 9d0df4a6ad..a0a977e037 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs @@ -54,9 +54,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation } /// - public CompilationResult Compile(IFileInfo fileInfo, string compilationContent) + public CompilationResult Compile([NotNull] IFileInfo fileInfo, [NotNull] string compilationContent) { - var syntaxTrees = new[] { SyntaxTreeGenerator.Generate(compilationContent, fileInfo.PhysicalPath) }; + // The path passed to SyntaxTreeGenerator.Generate is used by the compiler to generate symbols (pdb) that + // map to the source file. If a file does not exist on a physical file system, PhysicalPath will be null. + // This prevents files that exist in a non-physical file system from being debugged. + var path = fileInfo.PhysicalPath ?? fileInfo.Name; + var syntaxTrees = new[] { SyntaxTreeGenerator.Generate(compilationContent, path) }; var references = _applicationReferences.Value; diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewEngineOptionsTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewEngineOptionsTest.cs new file mode 100644 index 0000000000..8358e3ce2a --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewEngineOptionsTest.cs @@ -0,0 +1,49 @@ +// 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.Builder; +using Microsoft.AspNet.TestHost; +using RazorViewEngineOptionsWebsite; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class RazorViewEngineOptionsTest + { + private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(RazorViewEngineOptionsWebsite)); + private readonly Action _app = new Startup().Configure; + + [Fact] + public async Task RazorViewEngine_UsesFileSystemOnViewEngineOptionsToLocateViews() + { + // Arrange + var expectedMessage = "Hello test-user, this is /RazorViewEngineOptions_Home"; + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetStringAsync("http://localhost/RazorViewEngineOptions_Home?User=test-user"); + + // Assert + Assert.Equal(expectedMessage, response); + } + + [Fact] + public async Task RazorViewEngine_UsesFileSystemOnViewEngineOptionsToLocateAreaViews() + { + // Arrange + var expectedMessage = "Hello admin-user, this is /Restricted/RazorViewEngineOptions_Admin/Login"; + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var target = "http://localhost/Restricted/RazorViewEngineOptions_Admin/Login?AdminUser=admin-user"; + + // Act + var response = await client.GetStringAsync(target); + + // Assert + Assert.Equal(expectedMessage, response); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json index 6f3b83e729..b5bc4b1b20 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json @@ -21,6 +21,7 @@ "RoutingWebSite": "1.0.0", "RazorWebSite": "1.0.0", "RazorInstrumentationWebsite": "1.0.0", + "RazorViewEngineOptionsWebsite": "1.0.0", "RequestServicesWebSite": "1.0.0", "TagHelperSample.Web": "1.0.0", "TagHelpersWebSite": "1.0.0", diff --git a/test/WebSites/RazorViewEngineOptionsWebsite/Controllers/RazorViewEngineOptions_AdminController.cs b/test/WebSites/RazorViewEngineOptionsWebsite/Controllers/RazorViewEngineOptions_AdminController.cs new file mode 100644 index 0000000000..9d302ed2b2 --- /dev/null +++ b/test/WebSites/RazorViewEngineOptionsWebsite/Controllers/RazorViewEngineOptions_AdminController.cs @@ -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 Microsoft.AspNet.Mvc; + +namespace RazorViewEngineOptionsWebsite.Controllers +{ + [Area("Restricted")] + public class RazorViewEngineOptions_AdminController : Controller + { + public IActionResult Login() + { + return View(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RazorViewEngineOptionsWebsite/Controllers/RazorViewEngineOptions_HomeController.cs b/test/WebSites/RazorViewEngineOptionsWebsite/Controllers/RazorViewEngineOptions_HomeController.cs new file mode 100644 index 0000000000..3c7d7d6cd5 --- /dev/null +++ b/test/WebSites/RazorViewEngineOptionsWebsite/Controllers/RazorViewEngineOptions_HomeController.cs @@ -0,0 +1,15 @@ +// 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 RazorViewEngineOptionsWebsite +{ + public class RazorViewEngineOptions_HomeController : Controller + { + public IActionResult Index() + { + return View(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RazorViewEngineOptionsWebsite/EmbeddedResources/Areas/Restricted/Views/RazorViewEngineOptions_Admin/Login.cshtml b/test/WebSites/RazorViewEngineOptionsWebsite/EmbeddedResources/Areas/Restricted/Views/RazorViewEngineOptions_Admin/Login.cshtml new file mode 100644 index 0000000000..c6ea238bbf --- /dev/null +++ b/test/WebSites/RazorViewEngineOptionsWebsite/EmbeddedResources/Areas/Restricted/Views/RazorViewEngineOptions_Admin/Login.cshtml @@ -0,0 +1 @@ +Hello @Context.Request.Query["AdminUser"], this is @Url.Action() \ No newline at end of file diff --git a/test/WebSites/RazorViewEngineOptionsWebsite/EmbeddedResources/Views/RazorViewEngineOptions_Home/Index.cshtml b/test/WebSites/RazorViewEngineOptionsWebsite/EmbeddedResources/Views/RazorViewEngineOptions_Home/Index.cshtml new file mode 100644 index 0000000000..b673b5ff88 --- /dev/null +++ b/test/WebSites/RazorViewEngineOptionsWebsite/EmbeddedResources/Views/RazorViewEngineOptions_Home/Index.cshtml @@ -0,0 +1 @@ +Hello @Context.Request.Query["User"], this is @Url.Action() \ No newline at end of file diff --git a/test/WebSites/RazorViewEngineOptionsWebsite/RazorViewEngineOptionsWebsite.kproj b/test/WebSites/RazorViewEngineOptionsWebsite/RazorViewEngineOptionsWebsite.kproj new file mode 100644 index 0000000000..359e82f004 --- /dev/null +++ b/test/WebSites/RazorViewEngineOptionsWebsite/RazorViewEngineOptionsWebsite.kproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + b18ade62-35de-4a06-8e1d-edd6245f7f4d + + + + + + + 2.0 + 53986 + + + \ No newline at end of file diff --git a/test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs b/test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs new file mode 100644 index 0000000000..550d6b3894 --- /dev/null +++ b/test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs @@ -0,0 +1,39 @@ +// 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.IO; +using System.Reflection; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.Mvc.Razor; +using Microsoft.Framework.DependencyInjection; +using Microsoft.AspNet.Routing; + +namespace RazorViewEngineOptionsWebsite +{ + public class Startup + { + public void Configure(IApplicationBuilder app) + { + var configuration = app.GetTestConfiguration(); + + app.UseServices(services => + { + services.AddMvc(configuration); + + services.Configure(options => + { + options.FileSystem = new EmbeddedResourceFileSystem(GetType().GetTypeInfo().Assembly, "EmbeddedResources"); + }); + }); + + app.UseMvc(routes => + { + routes.MapRoute("areaRoute", "{area:exists}/{controller}/{action}"); + routes.MapRoute("default", + "{controller}/{action}/{id?}", + new { controller = "Home", action = "Index" }); + }); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RazorViewEngineOptionsWebsite/project.json b/test/WebSites/RazorViewEngineOptionsWebsite/project.json new file mode 100644 index 0000000000..441a3fba60 --- /dev/null +++ b/test/WebSites/RazorViewEngineOptionsWebsite/project.json @@ -0,0 +1,20 @@ +{ + "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" + }, + "resources": "EmbeddedResources/**", + "dependencies": { + "Kestrel": "1.0.0-*", + "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" + }, + "frameworks": { + "aspnet50": { }, + "aspnetcore50": { } + }, + "webroot": "wwwroot" +} diff --git a/test/WebSites/RazorViewEngineOptionsWebsite/wwwroot/readme.md b/test/WebSites/RazorViewEngineOptionsWebsite/wwwroot/readme.md new file mode 100644 index 0000000000..edb322b45b --- /dev/null +++ b/test/WebSites/RazorViewEngineOptionsWebsite/wwwroot/readme.md @@ -0,0 +1 @@ +Functional test for usage of RazorViewEngineOptions.FileSystem \ No newline at end of file From ce8d840cc6a6bb57d2fbaa751edc1c16df7e23f3 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 11 Dec 2014 10:09:42 -0800 Subject: [PATCH 015/118] Adding additional functional tests for ModelBinding --- .../ModelBindingTests.cs | 242 ++++++++++++++++++ ...Vehicle_PopulatesPropertyErrorsInViews.txt | 16 ++ ...alerVehicle_PopulatesValidationSummary.txt | 16 ++ .../UpdateDealerVehicle_UpdateSuccessful.txt | 24 ++ .../Controllers/VehicleController.cs | 54 ++++ .../Services/ILocationService.cs | 14 + .../Services/IVehicleService.cs | 12 + .../Services/LocationService.cs | 28 ++ .../Services/VehicleService.cs | 21 ++ test/WebSites/ModelBindingWebSite/Startup.cs | 13 +- .../ViewModels/DealerViewModel.cs | 22 ++ .../ViewModels/VehicleViewModel.cs | 50 ++++ .../ViewModels/VehicleWithDealerViewModel.cs | 39 +++ .../Views/Vehicle/UpdateSuccessful.cshtml | 26 ++ .../Views/Vehicle/UpdateVehicle.cshtml | 20 ++ 15 files changed, 589 insertions(+), 8 deletions(-) create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesPropertyErrorsInViews.txt create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesValidationSummary.txt create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_UpdateSuccessful.txt create mode 100644 test/WebSites/ModelBindingWebSite/Controllers/VehicleController.cs create mode 100644 test/WebSites/ModelBindingWebSite/Services/ILocationService.cs create mode 100644 test/WebSites/ModelBindingWebSite/Services/IVehicleService.cs create mode 100644 test/WebSites/ModelBindingWebSite/Services/LocationService.cs create mode 100644 test/WebSites/ModelBindingWebSite/Services/VehicleService.cs create mode 100644 test/WebSites/ModelBindingWebSite/ViewModels/DealerViewModel.cs create mode 100644 test/WebSites/ModelBindingWebSite/ViewModels/VehicleViewModel.cs create mode 100644 test/WebSites/ModelBindingWebSite/ViewModels/VehicleWithDealerViewModel.cs create mode 100644 test/WebSites/ModelBindingWebSite/Views/Vehicle/UpdateSuccessful.cshtml create mode 100644 test/WebSites/ModelBindingWebSite/Views/Vehicle/UpdateVehicle.cshtml diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs index 952d278ad1..08db805e1e 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs @@ -7,11 +7,14 @@ using System.Linq; using System.Linq.Expressions; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; using ModelBindingWebSite; +using ModelBindingWebSite.ViewModels; using Newtonsoft.Json; using Xunit; @@ -1083,5 +1086,244 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Should Update all included properties. Assert.Equal("March", user.RegisterationMonth); } + + [Fact] + public async Task UpdateVehicle_WithJson_ProducesModelStateErrors() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var content = new + { + Year = 3012, + InspectedDates = new[] + { + new DateTime(4065, 10, 10) + }, + Make = "Volttrax", + Model = "Epsum" + }; + + // Act + var response = await client.PutAsJsonAsync("http://localhost/api/vehicles/520", content); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var modelStateErrors = JsonConvert.DeserializeObject>>(body); + + Assert.Equal(3, modelStateErrors.Count); + Assert.Equal(new[] { + "The field Year must be between 1980 and 2034.", + "Year is invalid" + }, modelStateErrors["model.Year"]); + + var vinError = Assert.Single(modelStateErrors["model.Vin"]); + Assert.Equal("The Vin field is required.", vinError); + + var trackingIdError = Assert.Single(modelStateErrors["X-TrackingId"]); + Assert.Equal("A value is required but was not present in the request.", trackingIdError); + } + + [Fact] + public async Task UpdateVehicle_WithJson_DoesPropertyValidationPriorToValidationAtType() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var content = new + { + Year = 2007, + InspectedDates = new[] + { + new DateTime(4065, 10, 10) + }, + Make = "Volttrax", + Model = "Epsum", + Vin = "Pqrs" + }; + client.DefaultRequestHeaders.TryAddWithoutValidation("X-TrackingId", "trackingid"); + + // Act + var response = await client.PutAsJsonAsync("http://localhost/api/vehicles/520", content); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var modelStateErrors = JsonConvert.DeserializeObject>>(body); + + var item = Assert.Single(modelStateErrors); + Assert.Equal("model.InspectedDates", item.Key); + var error = Assert.Single(item.Value); + Assert.Equal("Inspection date cannot be later than year of manufacture.", error); + } + + [Fact] + public async Task UpdateVehicle_WithJson_BindsBodyAndServices() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var trackingId = Guid.NewGuid().ToString(); + var postedContent = new + { + Year = 2010, + InspectedDates = new List + { + new DateTime(2008, 10, 01), + new DateTime(2009, 03, 01), + }, + Make = "Volttrax", + Model = "Epsum", + Vin = "PQRS" + }; + client.DefaultRequestHeaders.TryAddWithoutValidation("X-TrackingId", trackingId); + + // Act + var response = await client.PutAsJsonAsync("http://localhost/api/vehicles/520", postedContent); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var actual = JsonConvert.DeserializeObject(body); + + Assert.Equal(postedContent.Vin, actual.Vin); + Assert.Equal(postedContent.Make, actual.Make); + Assert.Equal(postedContent.InspectedDates, actual.InspectedDates.Select(d => d.DateTime)); + Assert.Equal(trackingId, actual.LastUpdatedTrackingId); + } + + [Fact] + public async Task UpdateVehicle_WithXml_BindsBodyServicesAndHeaders() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var trackingId = Guid.NewGuid().ToString(); + var postedContent = new VehicleViewModel + { + Year = 2010, + InspectedDates = new DateTimeOffset[] + { + new DateTimeOffset(2008, 10, 01, 8, 3, 1, TimeSpan.Zero), + new DateTime(2009, 03, 01), + }, + Make = "Volttrax", + Model = "Epsum", + Vin = "PQRS" + }; + client.DefaultRequestHeaders.TryAddWithoutValidation("X-TrackingId", trackingId); + + // Act + var response = await client.PutAsXmlAsync("http://localhost/api/vehicles/520", postedContent); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var actual = JsonConvert.DeserializeObject(body); + + Assert.Equal(postedContent.Vin, actual.Vin); + Assert.Equal(postedContent.Make, actual.Make); + Assert.Equal(postedContent.InspectedDates, actual.InspectedDates); + Assert.Equal(trackingId, actual.LastUpdatedTrackingId); + } + + // Simulates a browser based client that does a Ajax post for partial page updates. + [Fact] + public async Task UpdateDealerVehicle_PopulatesPropertyErrorsInViews() + { + // Arrange + var expectedContent = await GetType().GetTypeInfo().Assembly.ReadResourceAsStringAsync( + "compiler/resources/UpdateDealerVehicle_PopulatesPropertyErrorsInViews.txt"); + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var postedContent = new + { + Year = 9001, + InspectedDates = new List + { + new DateTime(2008, 01, 01) + }, + Make = "Acme", + Model = "Epsum", + Vin = "LongerThan8Chars", + + }; + var url = "http://localhost/dealers/32/update-vehicle?dealer.name=TestCarDealer&dealer.location=SE"; + + // Act + var response = await client.PostAsJsonAsync(url, postedContent); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedContent, body); + } + + [Fact] + public async Task UpdateDealerVehicle_PopulatesValidationSummary() + { + // Arrange + var expectedContent = await GetType().GetTypeInfo().Assembly.ReadResourceAsStringAsync( + "compiler/resources/UpdateDealerVehicle_PopulatesValidationSummary.txt"); + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var postedContent = new + { + Year = 2013, + InspectedDates = new List + { + new DateTime(2008, 01, 01) + }, + Make = "Acme", + Model = "Epsum", + Vin = "8chars", + + }; + var url = "http://localhost/dealers/43/update-vehicle?dealer.name=TestCarDealer&dealer.location=SE"; + + // Act + var response = await client.PostAsJsonAsync(url, postedContent); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedContent, body); + } + + [Fact] + public async Task UpdateDealerVehicle_UsesDefaultValuesForOptionalProperties() + { + // Arrange + var expectedContent = await GetType().GetTypeInfo().Assembly.ReadResourceAsStringAsync( + "compiler/resources/UpdateDealerVehicle_UpdateSuccessful.txt"); + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var postedContent = new + { + Year = 2013, + InspectedDates = new DateTimeOffset[] + { + new DateTime(2008, 11, 01) + }, + Make = "RealSlowCars", + Model = "Epsum", + Vin = "8chars", + + }; + var url = "http://localhost/dealers/43/update-vehicle?dealer.name=TestCarDealer&dealer.location=NE"; + + // Act + var response = await client.PostAsJsonAsync(url, postedContent); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedContent, body); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesPropertyErrorsInViews.txt b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesPropertyErrorsInViews.txt new file mode 100644 index 0000000000..8802bb6ddb --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesPropertyErrorsInViews.txt @@ -0,0 +1,16 @@ +
+ TestCarDealer + SE + +
+
  • +
+
+ + The field Vin must be a string with a maximum length of 8. +
+
+ + The field Year must be between 1980 and 2034. +
+
\ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesValidationSummary.txt b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesValidationSummary.txt new file mode 100644 index 0000000000..21edd0399d --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesValidationSummary.txt @@ -0,0 +1,16 @@ +
+ TestCarDealer + SE + +
+
  • Make is invalid for region.
  • +
+
+ + +
+
+ + +
+
\ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_UpdateSuccessful.txt b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_UpdateSuccessful.txt new file mode 100644 index 0000000000..afe01b1f2e --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_UpdateSuccessful.txt @@ -0,0 +1,24 @@ +
+
    +
  • + Vin: 8chars +
  • +
  • + Inspected Dates: 11/1/2008 12:00:00 AM -07:00 +
  • +
+
+
+
    +
  • + Dealer: 43 +
  • +
  • + Phone: 999-99-0000 +
  • +
+
+ +
+Tracked by +
diff --git a/test/WebSites/ModelBindingWebSite/Controllers/VehicleController.cs b/test/WebSites/ModelBindingWebSite/Controllers/VehicleController.cs new file mode 100644 index 0000000000..b3b2575e55 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Controllers/VehicleController.cs @@ -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.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Net; +using Microsoft.AspNet.Mvc; +using ModelBindingWebSite.Services; +using ModelBindingWebSite.ViewModels; + +namespace ModelBindingWebSite +{ + public class VehicleController : Controller + { + [HttpPut("/api/vehicles/{id}")] + [Produces("application/json")] + public object UpdateVehicleApi( + [Range(1, 500)] int id, + [FromBody] VehicleViewModel model, + [FromServices] IVehicleService service, + [FromHeader(Name = "X-TrackingId")] string trackingId) + { + if (!ModelState.IsValid) + { + return SerializeModelState(); + } + + service.Update(id, model, trackingId); + + return model; + } + + [HttpPost("/dealers/{dealer.id:int}/update-vehicle")] + public IActionResult UpdateDealerVehicle(VehicleWithDealerViewModel model) + { + if (!ModelState.IsValid) + { + return PartialView("UpdateVehicle", model); + } + + model.Update(); + return PartialView("UpdateSuccessful", model); + } + + public IDictionary> SerializeModelState() + { + Response.StatusCode = (int)HttpStatusCode.BadRequest; + + return ModelState.Where(item => item.Value.Errors.Count > 0) + .ToDictionary(item => item.Key, item => item.Value.Errors.Select(e => e.ErrorMessage)); + } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Services/ILocationService.cs b/test/WebSites/ModelBindingWebSite/Services/ILocationService.cs new file mode 100644 index 0000000000..b637ac72dd --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Services/ILocationService.cs @@ -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. + +using ModelBindingWebSite.ViewModels; + +namespace ModelBindingWebSite.Services +{ + public interface ILocationService + { + bool IsValidMakeForRegion(string make, string region); + + bool Update(VehicleWithDealerViewModel model); + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Services/IVehicleService.cs b/test/WebSites/ModelBindingWebSite/Services/IVehicleService.cs new file mode 100644 index 0000000000..73f1b83772 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Services/IVehicleService.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. + +using ModelBindingWebSite.ViewModels; + +namespace ModelBindingWebSite.Services +{ + public interface IVehicleService + { + void Update(int id, VehicleViewModel vehicle, string trackingId); + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Services/LocationService.cs b/test/WebSites/ModelBindingWebSite/Services/LocationService.cs new file mode 100644 index 0000000000..46c8521bbc --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Services/LocationService.cs @@ -0,0 +1,28 @@ +// 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 ModelBindingWebSite.ViewModels; + +namespace ModelBindingWebSite.Services +{ + public class LocationService : ILocationService + { + public bool Update(VehicleWithDealerViewModel viewModel) + { + return true; + } + + public bool IsValidMakeForRegion(string make, string region) + { + switch (make) + { + case "Acme": + return region == "NW" || "region" == "South Central"; + case "FastCars": + return region != "Central"; + } + + return true; + } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Services/VehicleService.cs b/test/WebSites/ModelBindingWebSite/Services/VehicleService.cs new file mode 100644 index 0000000000..b01f1ccb31 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Services/VehicleService.cs @@ -0,0 +1,21 @@ +// 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 ModelBindingWebSite.ViewModels; + +namespace ModelBindingWebSite.Services +{ + public class VehicleService : IVehicleService + { + public void Update(int id, VehicleViewModel vehicle, string trackingId) + { + if (trackingId == null) + { + throw new ArgumentException(nameof(trackingId)); + } + + vehicle.LastUpdatedTrackingId = trackingId; + } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Startup.cs b/test/WebSites/ModelBindingWebSite/Startup.cs index b3c8067032..6a4e37e113 100644 --- a/test/WebSites/ModelBindingWebSite/Startup.cs +++ b/test/WebSites/ModelBindingWebSite/Startup.cs @@ -3,9 +3,8 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; -using Microsoft.Framework.Logging; +using ModelBindingWebSite.Services; namespace ModelBindingWebSite { @@ -28,17 +27,15 @@ namespace ModelBindingWebSite services.AddSingleton(); services.AddSingleton(); + + services.AddTransient(); + services.AddTransient(); }); app.UseErrorReporter(); // Add MVC to the request pipeline - app.UseMvc(routes => - { - routes.MapRoute("ActionAsMethod", "{controller}/{action}", - defaults: new { controller = "Home", action = "Index" }); - - }); + app.UseMvc(); } } } diff --git a/test/WebSites/ModelBindingWebSite/ViewModels/DealerViewModel.cs b/test/WebSites/ModelBindingWebSite/ViewModels/DealerViewModel.cs new file mode 100644 index 0000000000..2936243c81 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/ViewModels/DealerViewModel.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 System.ComponentModel.DataAnnotations; + +namespace ModelBindingWebSite.ViewModels +{ + public class DealerViewModel + { + private const string DefaultCustomerServiceNumber = "999-99-0000"; + + public int Id { get; set; } + + public string Name { get; set; } + + [Required] + public string Location { get; set; } + + [DataType(DataType.PhoneNumber)] + public string Phone { get; set; } = DefaultCustomerServiceNumber; + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/ViewModels/VehicleViewModel.cs b/test/WebSites/ModelBindingWebSite/ViewModels/VehicleViewModel.cs new file mode 100644 index 0000000000..907ccbe7fb --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/ViewModels/VehicleViewModel.cs @@ -0,0 +1,50 @@ +// 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.ComponentModel.DataAnnotations; +using System.Linq; + +namespace ModelBindingWebSite.ViewModels +{ + public class VehicleViewModel : IValidatableObject + { + [Required] + [StringLength(8)] + public string Vin { get; set; } + + public string Make { get; set; } + + public string Model { get; set; } + + [Range(1980, 2034)] + [CustomValidation(typeof(VehicleViewModel), nameof(ValidateYear))] + public int Year { get; set; } + + [Required] + [MaxLength(10)] + public DateTimeOffset[] InspectedDates { get; set; } + + public string LastUpdatedTrackingId { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (InspectedDates.Any(d => d.Year > Year)) + { + yield return new ValidationResult("Inspection date cannot be later than year of manufacture.", + new[] { nameof(InspectedDates) }); + } + } + + public static ValidationResult ValidateYear(int year) + { + if (year > DateTime.UtcNow.Year) + { + return new ValidationResult("Year is invalid"); + } + + return ValidationResult.Success; + } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/ViewModels/VehicleWithDealerViewModel.cs b/test/WebSites/ModelBindingWebSite/ViewModels/VehicleWithDealerViewModel.cs new file mode 100644 index 0000000000..951464ba0b --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/ViewModels/VehicleWithDealerViewModel.cs @@ -0,0 +1,39 @@ +// 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.ComponentModel.DataAnnotations; +using Microsoft.AspNet.Mvc; +using ModelBindingWebSite.Services; + +namespace ModelBindingWebSite.ViewModels +{ + public class VehicleWithDealerViewModel : IValidatableObject + { + [Required] + public DealerViewModel Dealer { get; set; } + + [Required] + [FromBody] + public VehicleViewModel Vehicle { get; set; } + + [FromServices] + public ILocationService LocationService { get; set; } + + [FromHeader(Name = "X-TrackingId")] + public string TrackingId { get; set; } = "default-tracking-id"; + + public IEnumerable Validate(ValidationContext validationContext) + { + if (!LocationService.IsValidMakeForRegion(Vehicle.Make, Dealer.Location)) + { + yield return new ValidationResult("Make is invalid for region."); + } + } + + public void Update() + { + LocationService.Update(this); + } + } +} \ No newline at end of file diff --git a/test/WebSites/ModelBindingWebSite/Views/Vehicle/UpdateSuccessful.cshtml b/test/WebSites/ModelBindingWebSite/Views/Vehicle/UpdateSuccessful.cshtml new file mode 100644 index 0000000000..402b269619 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Views/Vehicle/UpdateSuccessful.cshtml @@ -0,0 +1,26 @@ +@model ModelBindingWebSite.ViewModels.VehicleWithDealerViewModel + +
+
    +
  • + Vin: @Html.DisplayFor(m => m.Vehicle.Vin) +
  • +
  • + Inspected Dates: @Html.DisplayFor(m => m.Vehicle.InspectedDates) +
  • +
+
+
+
    +
  • + Dealer: @Html.DisplayFor(m => m.Dealer.Id) +
  • +
  • + Phone: @Html.DisplayFor(m => m.Dealer.Phone) +
  • +
+
+ +
+Tracked by @Model.TrackingId +
diff --git a/test/WebSites/ModelBindingWebSite/Views/Vehicle/UpdateVehicle.cshtml b/test/WebSites/ModelBindingWebSite/Views/Vehicle/UpdateVehicle.cshtml new file mode 100644 index 0000000000..d4bb27c707 --- /dev/null +++ b/test/WebSites/ModelBindingWebSite/Views/Vehicle/UpdateVehicle.cshtml @@ -0,0 +1,20 @@ +@model ModelBindingWebSite.ViewModels.VehicleWithDealerViewModel + +
+ @Model.Dealer.Name + @Model.Dealer.Location + @Html.HiddenFor(m => m.Dealer.Id) +
+@Html.ValidationSummary(excludePropertyErrors: true) +@using (Html.BeginForm()) +{ +
+ @Html.TextBoxFor(m => m.Vehicle.Vin) + @Html.ValidationMessageFor(m => m.Vehicle.Vin) +
+ +
+ @Html.EditorFor(m => m.Vehicle.Year) + @Html.ValidationMessageFor(m => m.Vehicle.Year) +
+} \ No newline at end of file From e0beec90f40c66f12d2213a8aa06a5165dbae755 Mon Sep 17 00:00:00 2001 From: Kai Ruhnau Date: Mon, 8 Dec 2014 09:58:12 +0100 Subject: [PATCH 016/118] Fixed folder name casing of Compiler/* --- .../Compiler/{PreProcess => Preprocess}/RazorPreCompilation.cs | 0 .../Resources}/BasicWebSite.Home.ActionLinkView.html | 0 .../resources => Compiler/Resources}/BasicWebSite.Home.Index.html | 0 .../Resources}/BasicWebSite.Home.PlainView.html | 0 .../Resources}/TagHelpersWebSite.Home.About.html | 0 .../Resources}/TagHelpersWebSite.Home.Help.html | 0 .../Resources}/TagHelpersWebSite.Home.Index.html | 0 .../Compiler/{PreProcess => Preprocess}/RazorPreCompilation.cs | 0 8 files changed, 0 insertions(+), 0 deletions(-) rename samples/MvcSample.Web/Compiler/{PreProcess => Preprocess}/RazorPreCompilation.cs (100%) rename test/Microsoft.AspNet.Mvc.FunctionalTests/{compiler/resources => Compiler/Resources}/BasicWebSite.Home.ActionLinkView.html (100%) rename test/Microsoft.AspNet.Mvc.FunctionalTests/{compiler/resources => Compiler/Resources}/BasicWebSite.Home.Index.html (100%) rename test/Microsoft.AspNet.Mvc.FunctionalTests/{compiler/resources => Compiler/Resources}/BasicWebSite.Home.PlainView.html (100%) rename test/Microsoft.AspNet.Mvc.FunctionalTests/{compiler/resources => Compiler/Resources}/TagHelpersWebSite.Home.About.html (100%) rename test/Microsoft.AspNet.Mvc.FunctionalTests/{compiler/resources => Compiler/Resources}/TagHelpersWebSite.Home.Help.html (100%) rename test/Microsoft.AspNet.Mvc.FunctionalTests/{compiler/resources => Compiler/Resources}/TagHelpersWebSite.Home.Index.html (100%) rename test/WebSites/PrecompilationWebSite/Compiler/{PreProcess => Preprocess}/RazorPreCompilation.cs (100%) diff --git a/samples/MvcSample.Web/Compiler/PreProcess/RazorPreCompilation.cs b/samples/MvcSample.Web/Compiler/Preprocess/RazorPreCompilation.cs similarity index 100% rename from samples/MvcSample.Web/Compiler/PreProcess/RazorPreCompilation.cs rename to samples/MvcSample.Web/Compiler/Preprocess/RazorPreCompilation.cs diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.ActionLinkView.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/BasicWebSite.Home.ActionLinkView.html similarity index 100% rename from test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.ActionLinkView.html rename to test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/BasicWebSite.Home.ActionLinkView.html diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.Index.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/BasicWebSite.Home.Index.html similarity index 100% rename from test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.Index.html rename to test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/BasicWebSite.Home.Index.html diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.PlainView.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/BasicWebSite.Home.PlainView.html similarity index 100% rename from test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.PlainView.html rename to test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/BasicWebSite.Home.PlainView.html diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Home.About.html similarity index 100% rename from test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html rename to test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Home.About.html diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Home.Help.html similarity index 100% rename from test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html rename to test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Home.Help.html diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Home.Index.html similarity index 100% rename from test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html rename to test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/TagHelpersWebSite.Home.Index.html diff --git a/test/WebSites/PrecompilationWebSite/Compiler/PreProcess/RazorPreCompilation.cs b/test/WebSites/PrecompilationWebSite/Compiler/Preprocess/RazorPreCompilation.cs similarity index 100% rename from test/WebSites/PrecompilationWebSite/Compiler/PreProcess/RazorPreCompilation.cs rename to test/WebSites/PrecompilationWebSite/Compiler/Preprocess/RazorPreCompilation.cs From 22ac7c45e509dd34cde2bc687548f421976b9089 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 17 Dec 2014 11:27:07 -0800 Subject: [PATCH 017/118] Update Mvc to use official xunit runner --- .../ViewDataDictionaryTest.cs | 7 +++++-- test/Microsoft.AspNet.Mvc.Core.Test/project.json | 4 ++-- test/Microsoft.AspNet.Mvc.FunctionalTests/project.json | 4 ++-- .../project.json | 4 ++-- test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json | 4 ++-- test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json | 4 ++-- test/Microsoft.AspNet.Mvc.Razor.Test/project.json | 4 ++-- test/Microsoft.AspNet.Mvc.TagHelpers.Test/project.json | 8 ++++---- test/Microsoft.AspNet.Mvc.Test/project.json | 4 ++-- .../project.json | 4 ++-- 10 files changed, 25 insertions(+), 22 deletions(-) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ViewDataDictionaryTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ViewDataDictionaryTest.cs index 8ba6e0e9f5..07b01e3904 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ViewDataDictionaryTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ViewDataDictionaryTest.cs @@ -137,8 +137,11 @@ namespace Microsoft.AspNet.Mvc.Core // Arrange var vdd = new ViewDataDictionary(new EmptyModelMetadataProvider()); - // Act & Assert - Assert.DoesNotThrow(() => { vdd.Model = model; }); + // Act + vdd.Model = model; + + // Assert + Assert.Same(model, vdd.Model); } [Fact] diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/project.json b/test/Microsoft.AspNet.Mvc.Core.Test/project.json index 5af82a46ad..4df41d5459 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.Core.Test/project.json @@ -6,10 +6,10 @@ "Microsoft.AspNet.Mvc" : "6.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", "Moq": "4.2.1312.1622", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" }, "frameworks": { "aspnet50": { } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json index b5bc4b1b20..5f50ff8f3b 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json @@ -34,10 +34,10 @@ "Microsoft.AspNet.TestHost": "1.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" }, "frameworks": { "aspnet50": { diff --git a/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/project.json b/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/project.json index 67778ba25e..f8d8b77fac 100644 --- a/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.HeaderValueAbstractions.Test/project.json @@ -2,10 +2,10 @@ "dependencies": { "Microsoft.AspNet.Mvc.HeaderValueAbstractions": "1.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" }, "frameworks": { "aspnet50": { }, diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json index 2abf9ab867..238ef8b091 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/project.json @@ -7,10 +7,10 @@ "Microsoft.AspNet.PipelineCore": "1.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", "Moq": "4.2.1312.1622", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" }, "frameworks": { "aspnet50": { }, diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json index d1f0acf5f6..ee6ce65e51 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/project.json @@ -3,10 +3,10 @@ "dependencies": { "Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" }, "frameworks": { "aspnet50": { diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/project.json b/test/Microsoft.AspNet.Mvc.Razor.Test/project.json index 9db98807c4..3166e67205 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/project.json @@ -7,10 +7,10 @@ "dependencies": { "Microsoft.AspNet.Mvc.Razor": "6.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" }, "frameworks": { "aspnet50": { diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/project.json b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/project.json index 549e7651fe..a05800beec 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/project.json @@ -1,11 +1,11 @@ { - "commands": { - "test": "Xunit.KRunner" - }, "dependencies": { "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" + }, + "commands": { + "test": "xunit.runner.kre" }, "frameworks": { "aspnet50": { diff --git a/test/Microsoft.AspNet.Mvc.Test/project.json b/test/Microsoft.AspNet.Mvc.Test/project.json index 55c791c321..0f116b9359 100644 --- a/test/Microsoft.AspNet.Mvc.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.Test/project.json @@ -4,10 +4,10 @@ }, "dependencies": { "Microsoft.AspNet.Mvc" : "6.0.0-*", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" }, "frameworks": { "aspnet50": { diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/project.json b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/project.json index 27d11bb19c..9aaf8cc023 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/project.json +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/project.json @@ -6,10 +6,10 @@ "Microsoft.AspNet.Mvc.WebApiCompatShim": "6.0.0-*", "Microsoft.AspNet.Testing": "1.0.0-*", "Moq": "4.2.1312.1622", - "Xunit.KRunner": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*" }, "commands": { - "test": "Xunit.KRunner" + "test": "xunit.runner.kre" }, "frameworks": { "aspnet50": {}, From 04c37a8d5137017b4ca7857e6f92d4f918d1f3be Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Thu, 11 Dec 2014 13:43:32 -0800 Subject: [PATCH 018/118] added few taghelper functional tests --- .../TagHelpersTest.cs | 76 +++++++++++++++++ ...elpersWebSite.Employee.Create.Invalid.html | 83 ++++++++++++++++++ .../TagHelpersWebSite.Employee.Create.html | 81 ++++++++++++++++++ ...sWebSite.Employee.Details.AfterCreate.html | 61 +++++++++++++ .../TagHelpersWebSite.Employee.Index.html | 42 +++++++++ .../Controllers/EmployeeController.cs | 35 ++++++++ .../TagHelpersWebSite/Models/Employee.cs | 32 +++++++ .../Views/Employee/Create.cshtml | 85 +++++++++++++++++++ .../Views/Employee/Details.cshtml | 64 ++++++++++++++ test/WebSites/TagHelpersWebSite/project.json | 2 +- 10 files changed, 560 insertions(+), 1 deletion(-) create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.Invalid.html create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.html create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Details.AfterCreate.html create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Index.html create mode 100644 test/WebSites/TagHelpersWebSite/Controllers/EmployeeController.cs create mode 100644 test/WebSites/TagHelpersWebSite/Models/Employee.cs create mode 100644 test/WebSites/TagHelpersWebSite/Views/Employee/Create.cshtml create mode 100644 test/WebSites/TagHelpersWebSite/Views/Employee/Details.cshtml diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/TagHelpersTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/TagHelpersTest.cs index 7b3fde1c22..25700cc545 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/TagHelpersTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/TagHelpersTest.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Net; +using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; using System.Threading.Tasks; @@ -72,5 +74,79 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Assert Assert.Equal(expected, result.Trim()); } + + [Fact] + public async Task ViewsWithModelMetadataAttributes_CanRenderForm() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + var expectedContent = await _resourcesAssembly.ReadResourceAsStringAsync( + "compiler/resources/TagHelpersWebSite.Employee.Create.html"); + + // Act + var response = await client.GetAsync("http://localhost/Employee/Create"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var responseContent = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedContent, responseContent); + } + + [Fact] + public async Task ViewsWithModelMetadataAttributes_CanRenderPostedValue() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + var expectedContent = await _resourcesAssembly.ReadResourceAsStringAsync( + "compiler/resources/TagHelpersWebSite.Employee.Details.AfterCreate.html"); + var validPostValues = new Dictionary + { + { "FullName", "Boo" }, + { "Gender", "M" }, + { "Age", "22" }, + { "EmployeeId", "0" }, + { "JoinDate", "2014-12-01" }, + { "Email", "a@b.com" }, + }; + var postContent = new FormUrlEncodedContent(validPostValues); + + // Act + var response = await client.PostAsync("http://localhost/Employee/Create", postContent); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var responseContent = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedContent, responseContent); + } + + [Fact] + public async Task ViewsWithModelMetadataAttributes_CanHandleInvalidData() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + var expectedContent = await _resourcesAssembly.ReadResourceAsStringAsync( + "compiler/resources/TagHelpersWebSite.Employee.Create.Invalid.html"); + var validPostValues = new Dictionary + { + { "FullName", "Boo" }, + { "Gender", "M" }, + { "Age", "1000" }, + { "EmployeeId", "0" }, + { "Email", "a@b.com" }, + { "Salary", "z" }, + }; + var postContent = new FormUrlEncodedContent(validPostValues); + + // Act + var response = await client.PostAsync("http://localhost/Employee/Create", postContent); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var responseContent = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedContent, responseContent); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.Invalid.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.Invalid.html new file mode 100644 index 0000000000..6fb84c0bc6 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.Invalid.html @@ -0,0 +1,83 @@ + + + + + + + + + + + Create + + + +
+ +
+

Employee

+
+
+
  • The field Age must be between 10 and 100.
  • +
  • The parameter conversion from type 'System.String' to type 'System.Int32' failed. See the inner exception for more information.
  • +
  • The JoinDate field is required.
  • +
+
+ +
+ + The field Age must be between 10 and 100. +
+
+
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ +
+ Male + Female + +
+
+
+ +
+ + The JoinDate field is required. +
+
+
+ +
+ + The parameter conversion from type 'System.String' to type 'System.Int32' failed. See the inner exception for more information. +
+
+ +
+
+ +
+
+
+
+ + + + + diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.html new file mode 100644 index 0000000000..85f318c70c --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.html @@ -0,0 +1,81 @@ + + + + + + + + + + + Create + + + +
+ +
+

Employee

+
+
+
  • +
+
+ +
+ + +
+
+
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ +
+ Male + Female + +
+
+
+ +
+ + +
+
+
+ +
+ + +
+
+ +
+
+ +
+
+
+
+ + + + + diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Details.AfterCreate.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Details.AfterCreate.html new file mode 100644 index 0000000000..7fce00fbb3 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Details.AfterCreate.html @@ -0,0 +1,61 @@ + + + + + + + + + Details + + + + +
+

Employee

+
+
+
+ Age +
+
+ 22 +
+
+ Email +
+
+ a@b.com +
+
+ Full Name +
+
+ Boo +
+
+ Gender +
+
+ M +
+
+ JoinDate +
+
+ 12/1/2014 +
+
+ Salary +
+
+ Not specified +
+
+
+

+ Back to List +

+ + + diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Index.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Index.html new file mode 100644 index 0000000000..d37cc0e127 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Index.html @@ -0,0 +1,42 @@ + + + + + + + + + Index + + + + +

+ Create New +

+ + + + + + + + + + +
+ Age + + Email + + Full Name + + Gender + + JoinDate + + Salary +
+ + + diff --git a/test/WebSites/TagHelpersWebSite/Controllers/EmployeeController.cs b/test/WebSites/TagHelpersWebSite/Controllers/EmployeeController.cs new file mode 100644 index 0000000000..b6e0442ce3 --- /dev/null +++ b/test/WebSites/TagHelpersWebSite/Controllers/EmployeeController.cs @@ -0,0 +1,35 @@ +// 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; +using Microsoft.AspNet.Mvc; +using TagHelpersWebSite.Models; + +namespace TagHelpersWebSite.Controllers +{ + public class EmployeeController : Controller + { + // GET: Employee + public string Index() + { + return "Index Page"; + } + + // GET: Employee/Create + public IActionResult Create() + { + return View(); + } + + // POST: Employee/Create + [HttpPost] + public IActionResult Create(Employee employee) + { + if (ModelState.IsValid) + { + return View("Details", employee); + } + return View(employee); + } + } +} \ No newline at end of file diff --git a/test/WebSites/TagHelpersWebSite/Models/Employee.cs b/test/WebSites/TagHelpersWebSite/Models/Employee.cs new file mode 100644 index 0000000000..2774480330 --- /dev/null +++ b/test/WebSites/TagHelpersWebSite/Models/Employee.cs @@ -0,0 +1,32 @@ +// 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.ComponentModel.DataAnnotations; + +namespace TagHelpersWebSite.Models +{ + public class Employee + { + public int EmployeeId { get; set; } + + [Display(Name = "Full Name", ShortName = "FN")] + public string FullName { get; set; } + + [DisplayFormat(NullDisplayText = "Not specified")] + public string Gender { get; set; } + + [Range(10, 100)] + public int Age { get; set; } + + [Required] + [DataType(DataType.Date)] + public DateTimeOffset JoinDate { get; set; } + + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + + [DisplayFormat(NullDisplayText = "Not specified")] + public int? Salary { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/TagHelpersWebSite/Views/Employee/Create.cshtml b/test/WebSites/TagHelpersWebSite/Views/Employee/Create.cshtml new file mode 100644 index 0000000000..f1f7600819 --- /dev/null +++ b/test/WebSites/TagHelpersWebSite/Views/Employee/Create.cshtml @@ -0,0 +1,85 @@ +@model TagHelpersWebSite.Models.Employee + +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers" + +@{ + Layout = null; + Html.Html5DateRenderingMode = Html5DateRenderingMode.Rfc3339; +} + + + + + + + + Create + + + +
+ +
+

Employee

+
+
+
+
+
+
+
+ +
+
+
+
+
+
+
+
+ +
+
+ +
+
+
+
+ + + + + diff --git a/test/WebSites/TagHelpersWebSite/Views/Employee/Details.cshtml b/test/WebSites/TagHelpersWebSite/Views/Employee/Details.cshtml new file mode 100644 index 0000000000..34420f35c4 --- /dev/null +++ b/test/WebSites/TagHelpersWebSite/Views/Employee/Details.cshtml @@ -0,0 +1,64 @@ +@model TagHelpersWebSite.Models.Employee + +@{ + Layout = null; +} + + + + + + + Details + + + + +
+

Employee

+
+
+
+ @Html.DisplayNameFor(model => model.Age) +
+
+ @Html.DisplayFor(model => model.Age) +
+
+ @Html.DisplayNameFor(model => model.Email) +
+
+ @Html.DisplayFor(model => model.Email) +
+
+ @Html.DisplayNameFor(model => model.FullName) +
+
+ @Html.DisplayFor(model => model.FullName) +
+
+ @Html.DisplayNameFor(model => model.Gender) +
+
+ @Html.DisplayFor(model => model.Gender) +
+
+ @Html.DisplayNameFor(model => model.JoinDate) +
+
+ @Html.DisplayFor(model => model.JoinDate) +
+
+ @Html.DisplayNameFor(model => model.Salary) +
+
+ @Html.DisplayFor(model => model.Salary) +
+
+
+

+ @Html.ActionLink("Back to List", "Index") +

+ + + diff --git a/test/WebSites/TagHelpersWebSite/project.json b/test/WebSites/TagHelpersWebSite/project.json index ce5714d371..ee2a5897fd 100644 --- a/test/WebSites/TagHelpersWebSite/project.json +++ b/test/WebSites/TagHelpersWebSite/project.json @@ -9,7 +9,7 @@ "Microsoft.AspNet.Mvc.TagHelpers": "6.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "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-*" }, "frameworks": { From c936ae80ca6b3df61e54e204444e088f5ced8051 Mon Sep 17 00:00:00 2001 From: SonjaKhan Date: Mon, 17 Nov 2014 10:52:33 -0800 Subject: [PATCH 019/118] Logging assemblies, controllers, and actions --- .../DefaultControllerModelBuilder.cs | 48 +++-- .../ControllerActionDescriptorProvider.cs | 27 ++- ...aultActionDescriptorsCollectionProvider.cs | 14 +- .../Logging/ActionConstraintValues.cs | 54 ++++++ .../Logging/ActionDescriptorValues.cs | 117 ++++++++++++ .../Logging/ActionModelValues.cs | 86 +++++++++ .../Logging/ApiExplorerModelValues.cs | 38 ++++ .../Logging/AssemblyValues.cs | 45 +++++ .../Logging/AttributeRouteInfoValues.cs | 42 +++++ .../Logging/AttributeRouteModelValues.cs | 51 +++++ .../Logging/ControllerModelValues.cs | 91 +++++++++ .../Logging/ControllerStatus.cs | 24 +++ .../Logging/FilterDescriptorValues.cs | 42 +++++ .../Logging/FilterValues.cs | 66 +++++++ .../Logging/IsControllerValues.cs | 36 ++++ .../Logging/LogFormatter.cs | 37 ---- .../Logging/ParameterDescriptorValues.cs | 42 +++++ .../Logging/ParameterModelValues.cs | 44 +++++ .../Logging/RouteConstraintAttributeValues.cs | 47 +++++ .../RouteDataActionConstraintValues.cs | 41 ++++ .../ActionConstraintValuesTest.cs | 84 +++++++++ .../ActionDescriptorValuesTest.cs | 23 +++ .../ActionModelValuesTest.cs | 24 +++ .../ApiExplorerModelValuesTest.cs | 20 ++ .../DefaultControllerModelBuilderTest.cs | 2 +- .../AttributeRouteInfoValuesTest.cs | 24 +++ .../AttributeRouteModelValuesTest.cs | 20 ++ ...ControllerActionDescriptorProviderTests.cs | 12 +- .../ControllerModelValuesTest.cs | 24 +++ ...DescriptorCollectionProviderLoggingTest.cs | 177 ++++++++++++++++++ .../DefaultActionSelectorTests.cs | 5 +- .../FilterDescriptorValuesTest.cs | 20 ++ .../FilterValuesTest.cs | 99 ++++++++++ .../KnownRouteValueConstraintTests.cs | 2 +- .../Logging/PropertiesAssert.cs | 33 ++++ .../RouteConstraintAttributeValuesTest.cs | 23 +++ .../RouteDataActionConstraintValuesTest.cs | 19 ++ .../StaticControllerModelBuilder.cs | 2 +- .../ApiControllerActionDiscoveryTest.cs | 6 +- 39 files changed, 1545 insertions(+), 66 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/ActionConstraintValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/ActionDescriptorValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/ApiExplorerModelValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/AssemblyValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteInfoValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteModelValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/ControllerStatus.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/FilterDescriptorValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/FilterValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/IsControllerValues.cs delete mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/LogFormatter.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/ParameterDescriptorValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/ParameterModelValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/RouteConstraintAttributeValues.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Logging/RouteDataActionConstraintValues.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ActionConstraintValuesTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ActionDescriptorValuesTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ActionModelValuesTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ApiExplorerModelValuesTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/AttributeRouteInfoValuesTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/AttributeRouteModelValuesTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ControllerModelValuesTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDescriptorCollectionProviderLoggingTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/FilterDescriptorValuesTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/FilterValuesTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/Logging/PropertiesAssert.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/RouteConstraintAttributeValuesTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/RouteDataActionConstraintValuesTest.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs index f58c3e1989..0e1dc06014 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs @@ -5,7 +5,9 @@ using System; using System.Linq; using System.Reflection; using Microsoft.AspNet.Mvc.Description; +using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.Routing; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc.ApplicationModels { @@ -15,14 +17,16 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public class DefaultControllerModelBuilder : IControllerModelBuilder { private readonly IActionModelBuilder _actionModelBuilder; + private readonly ILogger _logger; /// /// Creates a new . /// /// The used to create actions. - public DefaultControllerModelBuilder(IActionModelBuilder actionModelBuilder) + public DefaultControllerModelBuilder(IActionModelBuilder actionModelBuilder, ILoggerFactory loggerFactory) { _actionModelBuilder = actionModelBuilder; + _logger = loggerFactory.Create(); } /// @@ -61,24 +65,42 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels /// protected virtual bool IsController([NotNull] TypeInfo typeInfo) { - if (!typeInfo.IsClass || - typeInfo.IsAbstract || + var status = ControllerStatus.IsController; - // We only consider public top-level classes as controllers. IsPublic returns false for nested - // classes, regardless of visibility modifiers. - !typeInfo.IsPublic || - typeInfo.ContainsGenericParameters) + if (!typeInfo.IsClass) { - return false; + status |= ControllerStatus.IsNotAClass; + } + if (typeInfo.IsAbstract) + { + status |= ControllerStatus.IsAbstract; + } + // We only consider public top-level classes as controllers. IsPublic returns false for nested + // classes, regardless of visibility modifiers + if (!typeInfo.IsPublic) + { + status |= ControllerStatus.IsNotPublicOrTopLevel; + } + if (typeInfo.ContainsGenericParameters) + { + status |= ControllerStatus.ContainsGenericParameters; } - if (typeInfo.Name.Equals("Controller", StringComparison.OrdinalIgnoreCase)) { - return false; + status |= ControllerStatus.NameIsController; } - - return typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) || - typeof(Controller).GetTypeInfo().IsAssignableFrom(typeInfo); + if (!typeInfo.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && + !typeof(Controller).GetTypeInfo().IsAssignableFrom(typeInfo)) + { + status |= ControllerStatus.DoesNotEndWithControllerAndIsNotAssignable; + } + if (_logger.IsEnabled(LogLevel.Verbose)) + { + _logger.WriteVerbose(new IsControllerValues( + typeInfo.AsType(), + status)); + } + return status == ControllerStatus.IsController; } /// diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs index c5bffe2322..c727fea474 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs @@ -5,7 +5,9 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNet.Mvc.ApplicationModels; +using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.Filters; +using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc @@ -16,16 +18,19 @@ namespace Microsoft.AspNet.Mvc private readonly IAssemblyProvider _assemblyProvider; private readonly IReadOnlyList _globalFilters; private readonly IEnumerable _modelConventions; + private readonly ILogger _logger; - public ControllerActionDescriptorProvider(IAssemblyProvider assemblyProvider, - IControllerModelBuilder applicationModelBuilder, - IGlobalFilterProvider globalFilters, - IOptions optionsAccessor) + public ControllerActionDescriptorProvider([NotNull] IAssemblyProvider assemblyProvider, + [NotNull] IControllerModelBuilder applicationModelBuilder, + [NotNull] IGlobalFilterProvider globalFilters, + [NotNull] IOptions optionsAccessor, + [NotNull] ILoggerFactory loggerFactory) { _assemblyProvider = assemblyProvider; _applicationModelBuilder = applicationModelBuilder; _globalFilters = globalFilters.Filters; _modelConventions = optionsAccessor.Options.ApplicationModelConventions; + _logger = loggerFactory.Create(); } public int Order @@ -43,6 +48,13 @@ namespace Microsoft.AspNet.Mvc { var applicationModel = BuildModel(); ApplicationModelConventions.ApplyConventions(applicationModel, _modelConventions); + if (_logger.IsEnabled(LogLevel.Verbose)) + { + foreach (var controller in applicationModel.Controllers) + { + _logger.WriteVerbose(new ControllerModelValues(controller)); + } + } return ControllerActionDescriptorBuilder.Build(applicationModel); } @@ -53,6 +65,13 @@ namespace Microsoft.AspNet.Mvc var assemblies = _assemblyProvider.CandidateAssemblies; var types = assemblies.SelectMany(a => a.DefinedTypes); + if (_logger.IsEnabled(LogLevel.Verbose)) + { + foreach (var assembly in assemblies) + { + _logger.WriteVerbose(new AssemblyValues(assembly)); + } + } foreach (var type in types) { diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultActionDescriptorsCollectionProvider.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultActionDescriptorsCollectionProvider.cs index f48415b83c..0ab1a13760 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultActionDescriptorsCollectionProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultActionDescriptorsCollectionProvider.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNet.Mvc.Logging; using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc { @@ -13,15 +15,17 @@ namespace Microsoft.AspNet.Mvc public class DefaultActionDescriptorsCollectionProvider : IActionDescriptorsCollectionProvider { private readonly IServiceProvider _serviceProvider; + private readonly ILogger _logger; private ActionDescriptorsCollection _collection; /// /// Initializes a new instance of the class. /// /// The application IServiceProvider. - public DefaultActionDescriptorsCollectionProvider(IServiceProvider serviceProvider) + public DefaultActionDescriptorsCollectionProvider(IServiceProvider serviceProvider, ILoggerFactory factory) { _serviceProvider = serviceProvider; + _logger = factory.Create(); } /// @@ -48,6 +52,14 @@ namespace Microsoft.AspNet.Mvc actionDescriptorProvider.Invoke(actionDescriptorProviderContext); + if (_logger.IsEnabled(LogLevel.Verbose)) + { + foreach (var actionDescriptor in actionDescriptorProviderContext.Results) + { + _logger.WriteVerbose(new ActionDescriptorValues(actionDescriptor)); + } + } + return new ActionDescriptorsCollection(actionDescriptorProviderContext.Results, 0); } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ActionConstraintValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionConstraintValues.cs new file mode 100644 index 0000000000..9b68e6729b --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionConstraintValues.cs @@ -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; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Logging representation of an . + /// + public class ActionConstraintValues : LoggerStructureBase + { + public ActionConstraintValues(IActionConstraintMetadata inner) + { + var constraint = inner as IActionConstraint; + if (constraint != null) + { + IsConstraint = true; + Order = constraint.Order; + } + if (inner is IActionConstraintFactory) + { + IsFactory = true; + } + ActionConstraintMetadataType = inner.GetType(); + } + + /// + /// The of this . + /// + public Type ActionConstraintMetadataType { get; } + + /// + /// The constraint order if this is an . See . + /// + public int Order { get; } + + /// + /// Whether the action constraint is an . + /// + public bool IsConstraint { get; } + + /// + /// Whether the action constraint is an . + /// + public bool IsFactory { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ActionDescriptorValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionDescriptorValues.cs new file mode 100644 index 0000000000..0e7c488ea2 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionDescriptorValues.cs @@ -0,0 +1,117 @@ +// 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.Reflection; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Logging representation of the state of an or + /// . Logged during action discovery. + /// + public class ActionDescriptorValues : LoggerStructureBase + { + public ActionDescriptorValues([NotNull] ActionDescriptor inner) + { + Name = inner.Name; + DisplayName = inner.DisplayName; + Parameters = inner.Parameters.Select(p => new ParameterDescriptorValues(p)).ToList(); + FilterDescriptors = inner.FilterDescriptors.Select(f => new FilterDescriptorValues(f)).ToList(); + RouteConstraints = inner.RouteConstraints.Select(r => new RouteDataActionConstraintValues(r)).ToList(); + AttributeRouteInfo = new AttributeRouteInfoValues(inner.AttributeRouteInfo); + RouteValueDefaults = inner.RouteValueDefaults.ToDictionary(i => i.Key, i => i.Value.ToString()); + ActionConstraints = inner.ActionConstraints?.Select(a => new ActionConstraintValues(a))?.ToList(); + HttpMethods = inner.ActionConstraints?.OfType().SelectMany(c => c.HttpMethods).ToList(); + Properties = inner.Properties.ToDictionary(i => i.Key.ToString(), i => i.Value.GetType()); + var controllerActionDescriptor = inner as ControllerActionDescriptor; + if (controllerActionDescriptor != null) + { + MethodInfo = controllerActionDescriptor.MethodInfo; + ControllerName = controllerActionDescriptor.ControllerName; + ControllerTypeInfo = controllerActionDescriptor.ControllerTypeInfo; + } + } + + /// + /// The name of the action. See . + /// + public string Name { get; } + + /// + /// A friendly name for the action. See . + /// + public string DisplayName { get; } + + /// + /// The parameters of the action as . + /// See . + /// + public List Parameters { get; } + + /// + /// The filters of the action as . + /// See . + /// + public List FilterDescriptors { get; } + + /// + /// The route constraints of the action as . + /// See + /// + public List RouteConstraints { get; } + + /// + /// The attribute route info of the action as . + /// See . + /// + public AttributeRouteInfoValues AttributeRouteInfo { get; } + + /// + /// See . + /// + public Dictionary RouteValueDefaults { get; } + + /// + /// The action constraints of the action as . + /// See . + /// + public List ActionConstraints { get; } + + /// + /// The http methods this action supports. + /// + public List HttpMethods { get; } + + /// + /// See . + /// + public Dictionary Properties { get; } + + /// + /// The method info of the action if this is a . + /// See . + /// + public MethodInfo MethodInfo { get; } + + /// + /// The name of the action's controller if this is a . + /// See . + /// + public string ControllerName { get; } + + /// + /// The type info of the action's controller if this is a . + /// See . + /// + public TypeInfo ControllerTypeInfo { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs new file mode 100644 index 0000000000..a4c5344f01 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs @@ -0,0 +1,86 @@ +// 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.Linq; +using System.Reflection; +using Microsoft.AspNet.Mvc.ApplicationModels; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Represents the state of an . + /// Logged as a substructure of + /// + public class ActionModelValues : LoggerStructureBase + { + // note: omit the controller as this structure is nested inside the ControllerModelValues it belongs to + public ActionModelValues(ActionModel inner) + { + if (inner != null) + { + ActionName = inner.ActionName; + ActionMethod = inner.ActionMethod; + ApiExplorer = new ApiExplorerModelValues(inner.ApiExplorer); + Parameters = inner.Parameters.Select(p => new ParameterModelValues(p)).ToList(); + Filters = inner.Filters.Select(f => new FilterValues(f)).ToList(); + if (inner.AttributeRouteModel != null) + { + AttributeRouteModel = new AttributeRouteModelValues(inner.AttributeRouteModel); + } + HttpMethods = inner.HttpMethods; + ActionConstraints = inner.ActionConstraints?.Select(a => new ActionConstraintValues(a))?.ToList(); + } + } + + /// + /// The name of the action. See . + /// + public string ActionName { get; } + + /// + /// The method info of the action. See . + /// + public MethodInfo ActionMethod { get; } + + /// + /// See . + /// + public ApiExplorerModelValues ApiExplorer { get; } + + /// + /// The parameters of the action as . + /// See . + /// + public List Parameters { get; } + + /// + /// The filters of the action as . + /// See . + /// + public List Filters { get; } + + /// + /// The attribute route model of the action as . + /// See . + /// + public AttributeRouteModelValues AttributeRouteModel { get; } + + /// + /// The http methods this action supports. See . + /// + public List HttpMethods { get; } + + /// + /// The action constraints of the action as . + /// See . + /// + public List ActionConstraints { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ApiExplorerModelValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ApiExplorerModelValues.cs new file mode 100644 index 0000000000..8cf64ae7a1 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ApiExplorerModelValues.cs @@ -0,0 +1,38 @@ +// 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.ApplicationModels; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Logging representation of an . + /// + public class ApiExplorerModelValues : LoggerStructureBase + { + public ApiExplorerModelValues(ApiExplorerModel inner) + { + if (inner != null) + { + IsVisible = inner.IsVisible; + GroupName = inner.GroupName; + } + } + + /// + /// See . + /// + public bool? IsVisible { get; } + + /// + /// See . + /// + public string GroupName { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/AssemblyValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/AssemblyValues.cs new file mode 100644 index 0000000000..cc1dae48f7 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/AssemblyValues.cs @@ -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 System.Reflection; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Logging representation of the state of an . Logged during Assembly discovery in Startup. + /// + public class AssemblyValues : LoggerStructureBase + { + public AssemblyValues([NotNull] Assembly inner) + { + AssemblyName = inner.FullName; +#if ASPNET50 + Location = inner.Location; +#endif + IsDynamic = inner.IsDynamic; + } + + /// + /// The name of the assembly. See . + /// + public string AssemblyName { get; } + +#if ASPNET50 + /// + /// The location of the assembly. See . + /// + public string Location { get; } +#endif + + /// + /// Whether or not the assembly is dynamic. See . + /// + public bool IsDynamic { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteInfoValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteInfoValues.cs new file mode 100644 index 0000000000..ae983214ff --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteInfoValues.cs @@ -0,0 +1,42 @@ +// 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.Routing; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Logging representation of the state of a . Logged as a substructure of + /// . + /// + public class AttributeRouteInfoValues : LoggerStructureBase + { + public AttributeRouteInfoValues(AttributeRouteInfo inner) + { + Template = inner?.Template; + Order = inner?.Order; + Name = inner?.Name; + } + + /// + /// The route template. See . + /// + public string Template { get; } + + /// + /// The order of the route. See . + /// + public int? Order { get; } + + /// + /// The name of the route. See . + /// + public string Name { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteModelValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteModelValues.cs new file mode 100644 index 0000000000..7a5167b4f7 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteModelValues.cs @@ -0,0 +1,51 @@ +// 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; +using Microsoft.AspNet.Mvc.ApplicationModels; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Logging representation of the state of a . Logged as a substructure of + /// . + /// + public class AttributeRouteModelValues : LoggerStructureBase + { + public AttributeRouteModelValues(AttributeRouteModel inner) + { + if (inner != null) + { + Template = inner.Template; + Order = inner.Order; + Name = inner.Name; + IsAbsoluteTemplate = inner.IsAbsoluteTemplate; + } + } + + /// + /// The template of the route. See . + /// + public string Template { get; } + + /// + /// The order of the route. See . + /// + public int? Order { get; } + + /// + /// The name of the route. See . + /// + public string Name { get; } + + /// + /// Whether or not the template is absolute. See . + /// + public bool IsAbsoluteTemplate { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs new file mode 100644 index 0000000000..7682934bb3 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs @@ -0,0 +1,91 @@ +// 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.Framework.Logging; +using Microsoft.AspNet.Mvc.ApplicationModels; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Logging representation of the state of a . Logged during controller discovery. + /// + public class ControllerModelValues : LoggerStructureBase + { + public ControllerModelValues(ControllerModel inner) + { + if (inner != null) + { + ControllerName = inner.ControllerName; + ControllerType = inner.ControllerType.AsType(); + ApiExplorer = new ApiExplorerModelValues(inner.ApiExplorer); + Actions = inner.Actions.Select(a => new ActionModelValues(a)).ToList(); + Attributes = inner.Attributes.Select(a => a.GetType()).ToList(); + Filters = inner.Filters.Select(f => new FilterValues(f)).ToList(); + ActionConstraints = inner.ActionConstraints?.Select(a => new ActionConstraintValues(a))?.ToList(); + RouteConstraints = inner.RouteConstraints.Select( + r => new RouteConstraintAttributeValues(r)).ToList(); + AttributeRoutes = inner.AttributeRoutes.Select( + a => new AttributeRouteModelValues(a)).ToList(); + } + } + + /// + /// The name of the controller. See . + /// + public string ControllerName { get; } + + /// + /// The of the controller. See . + /// + public Type ControllerType { get; } + + /// + /// See . + /// + public ApiExplorerModelValues ApiExplorer { get; } + + /// + /// The actions of the controller as . + /// See . + /// + public List Actions { get; } + + /// + /// The s of the controller's attributes. + /// See . + /// + public List Attributes { get; } + + /// + /// The filters on the controller as . + /// See . + /// + public List Filters { get; } + + /// + /// The action constraints on the controller as . + /// See . + /// + public List ActionConstraints { get; } + + /// + /// The route constraints on the controller as . + /// See . + /// + public List RouteConstraints { get; set; } + + /// + /// The attribute routes on the controller as . + /// See . + /// + public List AttributeRoutes { get; set; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerStatus.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerStatus.cs new file mode 100644 index 0000000000..d64b8f8483 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerStatus.cs @@ -0,0 +1,24 @@ +// 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 +{ + /// + /// Indicates the status of a class during controller discovery. + /// All values except 0 represent a reason why a type is not a controller. + /// + [Flags] + public enum ControllerStatus + { + IsController = 0, + IsNotAClass = 1, + IsNotPublicOrTopLevel = 2, + IsAbstract = 4, + ContainsGenericParameters = 8, + // The name of the controller class is "Controller" + NameIsController = 16, + DoesNotEndWithControllerAndIsNotAssignable = 32 + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/FilterDescriptorValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/FilterDescriptorValues.cs new file mode 100644 index 0000000000..53c3a95f18 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/FilterDescriptorValues.cs @@ -0,0 +1,42 @@ +// 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 +{ + /// + /// Logging representation of the state of a . Logged as a substructure of + /// . + /// + public class FilterDescriptorValues : LoggerStructureBase + { + public FilterDescriptorValues([NotNull] FilterDescriptor inner) + { + Filter = new FilterValues(inner.Filter); + Order = inner.Order; + Scope = inner.Scope; + } + + /// + /// The instance of the filter descriptor as . + /// See . + /// + public FilterValues Filter { get; } + + /// + /// The filter order. See . + /// + public int Order { get; } + + /// + /// The filter scope. See . + /// + public int Scope { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/FilterValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/FilterValues.cs new file mode 100644 index 0000000000..60151950cb --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/FilterValues.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Logging representation of an . Logged as a component of + /// , and as a substructure of + /// and . + /// + public class FilterValues : LoggerStructureBase + { + public FilterValues(IFilter inner) + { + FilterMetadataType = inner.GetType(); + if (inner is IFilterFactory) + { + IsFactory = true; + if (inner is ServiceFilterAttribute) + { + FilterType = ((ServiceFilterAttribute)inner).ServiceType; + } + else if (inner is TypeFilterAttribute) + { + FilterType = ((TypeFilterAttribute)inner).ImplementationType; + } + } + if (FilterType != null) + { + FilterInterfaces = FilterType.GetInterfaces().ToList(); + } + else + { + FilterInterfaces = FilterMetadataType.GetInterfaces().ToList(); + } + } + + /// + /// Whether or not the instance of is an . + /// + public bool IsFactory { get; } + + /// + /// The metadata type of the . + /// + public Type FilterMetadataType { get; } + + /// + /// The inner of the if it is a + /// or . + /// + public Type FilterType { get; } + + /// + /// A list of interfaces the implements. + /// + public List FilterInterfaces { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/IsControllerValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/IsControllerValues.cs new file mode 100644 index 0000000000..3d6ddb8b9e --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/IsControllerValues.cs @@ -0,0 +1,36 @@ +// 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.Logging +{ + /// + /// Logged to indicate the state of a class during controller discovery. Logs the type + /// of the controller as well as the . + /// + public class IsControllerValues : LoggerStructureBase + { + public IsControllerValues(Type type, ControllerStatus status) + { + Type = type; + Status = status; + } + + /// + /// The of the potential class. + /// + public Type Type { get; } + + /// + /// The of the . + /// + public ControllerStatus Status { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ 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 deleted file mode 100644 index e9d1e7d0a1..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/LogFormatter.cs +++ /dev/null @@ -1,37 +0,0 @@ -// 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/ParameterDescriptorValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterDescriptorValues.cs new file mode 100644 index 0000000000..3a8cc407a0 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterDescriptorValues.cs @@ -0,0 +1,42 @@ +// 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.Logging +{ + /// + /// Logging representation of a . Logged as a substructure of + /// . + /// + public class ParameterDescriptorValues : LoggerStructureBase + { + public ParameterDescriptorValues([NotNull] ParameterDescriptor inner) + { + ParameterName = inner.Name; + ParameterType = inner.ParameterType; + BinderMetadataType = inner.BinderMetadata?.GetType(); + } + + /// + /// The name of the parameter. See . + /// + public string ParameterName { get; } + + /// + /// The of the parameter. See . + /// + public Type ParameterType { get; } + + /// + /// The of the . + /// + public Type BinderMetadataType { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterModelValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterModelValues.cs new file mode 100644 index 0000000000..62b179ece5 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ParameterModelValues.cs @@ -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; +using Microsoft.AspNet.Mvc.ApplicationModels; +using Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Logging representation of a . Logged as a substructure of + /// , this contains the name, type, and + /// binder metadata of the parameter. + /// + public class ParameterModelValues : LoggerStructureBase + { + public ParameterModelValues([NotNull] ParameterModel inner) + { + ParameterName = inner.ParameterName; + ParameterType = inner.ParameterInfo.ParameterType; + BinderMetadata = inner.BinderMetadata?.GetType(); + } + + /// + /// The name of the parameter. See . + /// + public string ParameterName { get; } + + /// + /// The of the parameter. + /// + public Type ParameterType { get; } + + /// + /// The of the . + /// + public Type BinderMetadata { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/RouteConstraintAttributeValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/RouteConstraintAttributeValues.cs new file mode 100644 index 0000000000..75638d97b8 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/RouteConstraintAttributeValues.cs @@ -0,0 +1,47 @@ +// 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 +{ + /// + /// Logging representation of a . Logged as a substructure of + /// + /// + public class RouteConstraintAttributeValues : LoggerStructureBase + { + public RouteConstraintAttributeValues([NotNull] RouteConstraintAttribute inner) + { + RouteKey = inner.RouteKey; + RouteValue = inner.RouteValue; + RouteKeyHandling = inner.RouteKeyHandling; + BlockNonAttributedActions = inner.BlockNonAttributedActions; + } + + /// + /// The route value key. See . + /// + public string RouteKey { get; } + + /// + /// The expected route value. See . + /// + public string RouteValue { get; } + + /// + /// The . See . + /// + public RouteKeyHandling RouteKeyHandling { get; } + + /// + /// See . + /// + public bool BlockNonAttributedActions { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/RouteDataActionConstraintValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/RouteDataActionConstraintValues.cs new file mode 100644 index 0000000000..e1e066d454 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/RouteDataActionConstraintValues.cs @@ -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 Microsoft.Framework.Logging; + +namespace Microsoft.AspNet.Mvc.Logging +{ + /// + /// Logging representation of the state of a . Logged as a substructure of + /// . + /// + public class RouteDataActionConstraintValues : LoggerStructureBase + { + public RouteDataActionConstraintValues([NotNull] RouteDataActionConstraint inner) + { + RouteKey = inner.RouteKey; + RouteValue = inner.RouteValue; + KeyHandling = inner.KeyHandling; + } + + /// + /// The route key. See . + /// + public string RouteKey { get; } + + /// + /// The route value. See . + /// + public string RouteValue { get; } + + /// + /// The . See . + /// + public RouteKeyHandling KeyHandling { get; } + + public override string Format() + { + return LogFormatter.FormatStructure(this); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionConstraintValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionConstraintValuesTest.cs new file mode 100644 index 0000000000..804673d333 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionConstraintValuesTest.cs @@ -0,0 +1,84 @@ +// 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 Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public class ActionConstraintValuesTest + { + [Fact] + public void IActionConstraintMetadata_InitializesCorrectValues() + { + // Arrange + var constraint = new TestConstraintMetadata(); + + // Act + var constraintValues = new ActionConstraintValues(constraint); + + // Assert + Assert.False(constraintValues.IsConstraint); + Assert.False(constraintValues.IsFactory); + Assert.Equal(0, constraintValues.Order); + Assert.Equal(typeof(TestConstraintMetadata), constraintValues.ActionConstraintMetadataType); + } + + [Fact] + public void IActionConstraint_InitializesCorrectValues() + { + // Arrange + var constraint = new TestConstraint(); + + // Act + var constraintValues = new ActionConstraintValues(constraint); + + // Assert + Assert.True(constraintValues.IsConstraint); + Assert.False(constraintValues.IsFactory); + Assert.Equal(23, constraintValues.Order); + Assert.Equal(typeof(TestConstraint), constraintValues.ActionConstraintMetadataType); + } + + [Fact] + public void IActionConstraintFactory_InitializesCorrectValues() + { + // Arrange + var constraint = new TestFactory(); + + // Act + var constraintValues = new ActionConstraintValues(constraint); + + // Assert + Assert.False(constraintValues.IsConstraint); + Assert.True(constraintValues.IsFactory); + Assert.Equal(0, constraintValues.Order); + Assert.Equal(typeof(TestFactory), constraintValues.ActionConstraintMetadataType); + } + + private class TestConstraintMetadata : IActionConstraintMetadata + { + } + + private class TestConstraint : IActionConstraint + { + public int Order + { + get { return 23; } + } + + public bool Accept(ActionConstraintContext context) + { + return false; + } + } + + private class TestFactory : IActionConstraintFactory + { + public IActionConstraint CreateInstance(IServiceProvider services) + { + return new TestConstraint(); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionDescriptorValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionDescriptorValuesTest.cs new file mode 100644 index 0000000000..9ad84e2a34 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionDescriptorValuesTest.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 Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public class ActionDescriptorValuesTest + { + [Fact] + public void ActionDescriptorValues_IncludesAllProperties() + { + // Arrange + var include = new[] { "HttpMethods" }; + + // Assert + PropertiesAssert.PropertiesAreTheSame( + typeof(ControllerActionDescriptor), + typeof(ActionDescriptorValues), + include: include); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionModelValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionModelValuesTest.cs new file mode 100644 index 0000000000..ec058efb7d --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionModelValuesTest.cs @@ -0,0 +1,24 @@ +// 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.ApplicationModels; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public class ActionModelValuesTest + { + [Fact] + public void ActionModelValues_IncludesAllProperties() + { + // Arrange + var exclude = new[] { "Controller", "Attributes", "IsActionNameMatchRequired" }; + + // Assert + PropertiesAssert.PropertiesAreTheSame( + typeof(ActionModel), + typeof(ActionModelValues), + exclude); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApiExplorerModelValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApiExplorerModelValuesTest.cs new file mode 100644 index 0000000000..8ff5eb427a --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApiExplorerModelValuesTest.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.AspNet.Mvc.ApplicationModels; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public class ApiExplorerModelValuesTest + { + [Fact] + public void ApiExplorerModelValues_IncludesAllProperties() + { + // Assert + PropertiesAssert.PropertiesAreTheSame( + typeof(ApiExplorerModel), + typeof(ApiExplorerModelValues)); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs index e02d2431c6..87481ef450 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs @@ -166,7 +166,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels private class AccessibleControllerModelBuilder : DefaultControllerModelBuilder { public AccessibleControllerModelBuilder() - : base(new DefaultActionModelBuilder()) + : base(new DefaultActionModelBuilder(), new NullLoggerFactory()) { } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/AttributeRouteInfoValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/AttributeRouteInfoValuesTest.cs new file mode 100644 index 0000000000..f9da445d10 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/AttributeRouteInfoValuesTest.cs @@ -0,0 +1,24 @@ +// 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.ApplicationModels; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public class AttributeRouteInfoValuesTest + { + [Fact] + public void AttributeRouteModelValues_IncludesAllProperties() + { + // Arrange + var exclude = new[] { "Attribute" }; + + // Assert + PropertiesAssert.PropertiesAreTheSame( + typeof(AttributeRouteModel), + typeof(AttributeRouteModelValues), + exclude); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/AttributeRouteModelValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/AttributeRouteModelValuesTest.cs new file mode 100644 index 0000000000..07b45ef980 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/AttributeRouteModelValuesTest.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.AspNet.Mvc.Routing; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public class AttributeRouteModelValuesTest + { + [Fact] + public void AttributeRouteInfoValues_IncludesAllProperties() + { + // Assert + PropertiesAssert.PropertiesAreTheSame( + typeof(AttributeRouteInfo), + typeof(AttributeRouteInfoValues)); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs index 286f311e67..6e9034a301 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs @@ -1340,7 +1340,8 @@ namespace Microsoft.AspNet.Mvc.Test assemblyProvider.Object, modelBuilder, new TestGlobalFilterProvider(filters), - new MockMvcOptionsAccessor()); + new MockMvcOptionsAccessor(), + new NullLoggerFactory()); return provider; } @@ -1359,7 +1360,8 @@ namespace Microsoft.AspNet.Mvc.Test assemblyProvider.Object, modelBuilder, new TestGlobalFilterProvider(), - new MockMvcOptionsAccessor()); + new MockMvcOptionsAccessor(), + new NullLoggerFactory()); return provider; } @@ -1379,7 +1381,8 @@ namespace Microsoft.AspNet.Mvc.Test assemblyProvider.Object, modelBuilder, new TestGlobalFilterProvider(), - options); + options, + new NullLoggerFactory()); } private IEnumerable GetDescriptors(params TypeInfo[] controllerTypeInfos) @@ -1395,7 +1398,8 @@ namespace Microsoft.AspNet.Mvc.Test assemblyProvider.Object, modelBuilder, new TestGlobalFilterProvider(), - new MockMvcOptionsAccessor()); + new MockMvcOptionsAccessor(), + new NullLoggerFactory()); return provider.GetDescriptors(); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerModelValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerModelValuesTest.cs new file mode 100644 index 0000000000..b1b089943e --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerModelValuesTest.cs @@ -0,0 +1,24 @@ +// 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.ApplicationModels; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public class ControllerModelValuesTest + { + [Fact] + public void ControllerModelValues_IncludesAllProperties() + { + // Arrange + var exclude = new[] { "Application" }; + + // Assert + PropertiesAssert.PropertiesAreTheSame( + typeof(ControllerModel), + typeof(ControllerModelValues), + exclude); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDescriptorCollectionProviderLoggingTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDescriptorCollectionProviderLoggingTest.cs new file mode 100644 index 0000000000..5e8761f81c --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionDescriptorCollectionProviderLoggingTest.cs @@ -0,0 +1,177 @@ +// 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.ComponentModel.Design; +using System.Linq; +using System.Reflection; +using Microsoft.AspNet.Mvc.ApplicationModels; +using Microsoft.Framework.DependencyInjection; +using Microsoft.Framework.DependencyInjection.NestedProviders; +using Microsoft.Framework.Logging; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public class DefaultActionDescriptorCollectionProviderLoggingTest + { + [Fact] + public void SimpleController_AssemblyDiscovery() + { + // Arrange + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink); + + // Act + var provider = GetProvider(loggerFactory, typeof(SimpleController).GetTypeInfo()); + provider.BuildModel(); + + // Assert + Assert.Single(sink.Writes); + + var assemblyValues = sink.Writes[0].State as AssemblyValues; + Assert.NotNull(assemblyValues); + Assert.True(assemblyValues.AssemblyName.Contains("Microsoft.AspNet.Mvc.Core.Test")); + } + + [Fact] + public void ControllerDiscovery() + { + // Arrange + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink); + + // Act + var provider = GetProvider( + loggerFactory, + typeof(SimpleController).GetTypeInfo(), + typeof(BasicController).GetTypeInfo()); + provider.GetDescriptors(); + + // Assert + // 1 assembly, 2 controllers + Assert.Equal(3, sink.Writes.Count); + + var controllerModelValues = sink.Writes[1].State as ControllerModelValues; + Assert.NotNull(controllerModelValues); + Assert.Equal("Simple", controllerModelValues.ControllerName); + Assert.Equal(typeof(SimpleController), controllerModelValues.ControllerType); + Assert.Single(controllerModelValues.Actions); + Assert.Empty(controllerModelValues.AttributeRoutes); + Assert.Empty(controllerModelValues.RouteConstraints); + Assert.Empty(controllerModelValues.Attributes); + Assert.Empty(controllerModelValues.Filters); + + controllerModelValues = sink.Writes[2].State as ControllerModelValues; + Assert.NotNull(controllerModelValues); + Assert.Equal("Basic", controllerModelValues.ControllerName); + Assert.Equal(typeof(BasicController), controllerModelValues.ControllerType); + Assert.Equal(2, controllerModelValues.Actions.Count); + Assert.Equal("GET", controllerModelValues.Actions[0].HttpMethods.FirstOrDefault()); + Assert.Equal("POST", controllerModelValues.Actions[1].HttpMethods.FirstOrDefault()); + Assert.Empty(controllerModelValues.AttributeRoutes); + Assert.Empty(controllerModelValues.RouteConstraints); + Assert.NotEmpty(controllerModelValues.Attributes); + Assert.Single(controllerModelValues.Filters); + } + + [Fact] + public void ActionDiscovery() + { + // Arrange + var sink = new TestSink(); + var loggerFactory = new TestLoggerFactory(sink); + + // Act + CreateActionDescriptors(loggerFactory, + typeof(SimpleController).GetTypeInfo(), + typeof(BasicController).GetTypeInfo()); + + // Assert + // 1 assembly, 2 controllers, 3 actions + Assert.Equal(6, sink.Writes.Count); + + var actionDescriptorValues = sink.Writes[3].State as ActionDescriptorValues; + Assert.NotNull(actionDescriptorValues); + Assert.Equal("EmptyAction", actionDescriptorValues.Name); + Assert.Equal("Simple", actionDescriptorValues.ControllerName); + Assert.Equal(typeof(SimpleController), actionDescriptorValues.ControllerTypeInfo); + Assert.Null(actionDescriptorValues.AttributeRouteInfo.Name); + Assert.Null(actionDescriptorValues.ActionConstraints); + Assert.Empty(actionDescriptorValues.FilterDescriptors); + Assert.Empty(actionDescriptorValues.Parameters); + + actionDescriptorValues = sink.Writes[4].State as ActionDescriptorValues; + Assert.NotNull(actionDescriptorValues); + Assert.Equal("Basic", actionDescriptorValues.Name); + Assert.Equal("Basic", actionDescriptorValues.ControllerName); + Assert.Equal(typeof(BasicController), actionDescriptorValues.ControllerTypeInfo); + Assert.Null(actionDescriptorValues.AttributeRouteInfo.Name); + Assert.NotEmpty(actionDescriptorValues.ActionConstraints); + Assert.Equal(2, actionDescriptorValues.FilterDescriptors.Count); + Assert.Empty(actionDescriptorValues.Parameters); + + actionDescriptorValues = sink.Writes[5].State as ActionDescriptorValues; + Assert.NotNull(actionDescriptorValues); + Assert.Equal("Basic", actionDescriptorValues.Name); + Assert.Equal("Basic", actionDescriptorValues.ControllerName); + Assert.Equal(typeof(BasicController), actionDescriptorValues.ControllerTypeInfo); + Assert.Null(actionDescriptorValues.AttributeRouteInfo.Name); + Assert.NotEmpty(actionDescriptorValues.ActionConstraints); + Assert.Single(actionDescriptorValues.FilterDescriptors); + Assert.Single(actionDescriptorValues.RouteConstraints); + Assert.Single(actionDescriptorValues.Parameters); + } + + private void CreateActionDescriptors(ILoggerFactory loggerFactory, params TypeInfo[] controllerTypeInfo) + { + var actionDescriptorProvider = GetProvider(loggerFactory, controllerTypeInfo); + var descriptorProvider = + new NestedProviderManager(new[] { actionDescriptorProvider }); + + var serviceContainer = new ServiceContainer(); + serviceContainer.AddService(typeof(INestedProviderManager), + descriptorProvider); + + var actionCollectionDescriptorProvider = new DefaultActionDescriptorsCollectionProvider(serviceContainer, loggerFactory); + var descriptors = actionCollectionDescriptorProvider.ActionDescriptors; + } + + private ControllerActionDescriptorProvider GetProvider( + ILoggerFactory loggerFactory, params TypeInfo[] controllerTypeInfo) + { + var modelBuilder = new StaticControllerModelBuilder(controllerTypeInfo); + + var assemblyProvider = new Mock(); + assemblyProvider + .SetupGet(ap => ap.CandidateAssemblies) + .Returns(new Assembly[] { controllerTypeInfo.First().Assembly }); + + var provider = new ControllerActionDescriptorProvider( + assemblyProvider.Object, + modelBuilder, + new TestGlobalFilterProvider(), + new MockMvcOptionsAccessor(), + loggerFactory); + + return provider; + } + + private class SimpleController + { + public void EmptyAction() { } + } + + [Authorize] + private class BasicController + { + [HttpGet] + [AllowAnonymous] + public void Basic() { } + + [HttpPost] + [Route("/Basic")] + public void Basic(int id) { } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs index c8b5b71b49..dddfeac1cc 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultActionSelectorTests.cs @@ -739,7 +739,7 @@ namespace Microsoft.AspNet.Mvc serviceContainer.AddService(typeof(INestedProviderManager), descriptorProvider); - var actionCollectionDescriptorProvider = new DefaultActionDescriptorsCollectionProvider(serviceContainer); + var actionCollectionDescriptorProvider = new DefaultActionDescriptorsCollectionProvider(serviceContainer, new NullLoggerFactory()); var decisionTreeProvider = new ActionSelectorDecisionTreeProvider(actionCollectionDescriptorProvider); var actionConstraintProvider = new NestedProviderManager( @@ -771,7 +771,8 @@ namespace Microsoft.AspNet.Mvc assemblyProvider, modelBuilder, new TestGlobalFilterProvider(), - new MockMvcOptionsAccessor()); + new MockMvcOptionsAccessor(), + new NullLoggerFactory()); } private static HttpContext GetHttpContext(string httpMethod) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/FilterDescriptorValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/FilterDescriptorValuesTest.cs new file mode 100644 index 0000000000..4850d4d5c8 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/FilterDescriptorValuesTest.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.AspNet.Mvc.Logging; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Test.Logging +{ + public class FilterDescriptorValuesTest + { + [Fact] + public void FilterDescriptorValues_IncludesAllProperties() + { + // Assert + PropertiesAssert.PropertiesAreTheSame( + typeof(FilterDescriptor), + typeof(FilterDescriptorValues)); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/FilterValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/FilterValuesTest.cs new file mode 100644 index 0000000000..446273d69e --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/FilterValuesTest.cs @@ -0,0 +1,99 @@ +// 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 Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public class FilterValuesTest + { + [Fact] + public void IFilter_InitializesCorrectValues() + { + // Arrange + var filter = new TestFilter(); + + // Act + var filterValues = new FilterValues(filter); + + // Assert + Assert.False(filterValues.IsFactory); + Assert.Null(filterValues.FilterType); + Assert.Equal(typeof(TestFilter), filterValues.FilterMetadataType); + Assert.Equal( + new List() { typeof(IFilter), typeof(IExceptionFilter) }, + filterValues.FilterInterfaces); + } + + [Fact] + public void IFilterFactory_InitializesCorrectValues() + { + // Arrange + var filter = new TestFactory(); + + // Act + var filterValues = new FilterValues(filter); + + // Assert + Assert.True(filterValues.IsFactory); + Assert.Null(filterValues.FilterType); + Assert.Equal(typeof(TestFactory), filterValues.FilterMetadataType); + Assert.Equal( + new List() { typeof(IFilterFactory), typeof(IFilter) }, + filterValues.FilterInterfaces); + } + + [Fact] + public void ServiceFilterAttribute_InitializesCorrectValues() + { + // Arrange + var filter = new ServiceFilterAttribute(typeof(TestFilter)); + + // Act + var filterValues = new FilterValues(filter); + + // Assert + Assert.True(filterValues.IsFactory); + Assert.Equal(typeof(TestFilter), filterValues.FilterType); + Assert.Equal(typeof(ServiceFilterAttribute), filterValues.FilterMetadataType); + Assert.Equal( + new List() { typeof(IFilter), typeof(IExceptionFilter) }, + filterValues.FilterInterfaces); + } + + [Fact] + public void TypeFilterAttribute_InitializesCorrectValues() + { + // Arrange + var filter = new TypeFilterAttribute(typeof(TestFilter)); + + // Act + var filterValues = new FilterValues(filter); + + // Assert + Assert.True(filterValues.IsFactory); + Assert.Equal(typeof(TestFilter), filterValues.FilterType); + Assert.Equal(typeof(TypeFilterAttribute), filterValues.FilterMetadataType); + Assert.Equal( + new List() { typeof(IFilter), typeof(IExceptionFilter) }, + filterValues.FilterInterfaces); + } + + private class TestFilter : IFilter, IExceptionFilter + { + public void OnException(ExceptionContext context) + { + } + } + + private class TestFactory : IFilterFactory + { + public IFilter CreateInstance(IServiceProvider serviceProvider) + { + return new TestFilter(); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/KnownRouteValueConstraintTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/KnownRouteValueConstraintTests.cs index 9afcdac612..4cda51fa84 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/KnownRouteValueConstraintTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/KnownRouteValueConstraintTests.cs @@ -164,7 +164,7 @@ namespace Microsoft.AspNet.Routing.Tests .Returns(actionProvider.Object); context.Setup(o => o.RequestServices .GetService(typeof(IActionDescriptorsCollectionProvider))) - .Returns(new DefaultActionDescriptorsCollectionProvider(context.Object.RequestServices)); + .Returns(new DefaultActionDescriptorsCollectionProvider(context.Object.RequestServices, new NullLoggerFactory())); return context.Object; } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Logging/PropertiesAssert.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/PropertiesAssert.cs new file mode 100644 index 0000000000..851883accd --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Logging/PropertiesAssert.cs @@ -0,0 +1,33 @@ +using System; +using System.Linq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public static class PropertiesAssert + { + /// + /// Given two types, compares their properties and asserts true if they have the same property names. + /// + /// The original type to compare against. + /// The shadow type whose properties will be compared against the original. + /// Properties that exist in the original type but not the shadow. + /// Properties that are in the shadow type but not in the original. + public static void PropertiesAreTheSame(Type original, Type shadow, string[] exclude = null, string[] include = null) + { + var originalProperties = original.GetProperties().Where(p => !exclude?.Contains(p.Name) ?? true) + .Select(p => p.Name); + if (include != null) + { + originalProperties = originalProperties.Concat(include.ToList()); + } + originalProperties = originalProperties.OrderBy(n => n); + + // Message is a property on all ILoggerStructures + var shadowProperties = shadow.GetProperties().Where(p => !string.Equals("Message", p.Name)) + .Select(p => p.Name).OrderBy(n => n); + + Assert.True(originalProperties.SequenceEqual(shadowProperties)); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/RouteConstraintAttributeValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/RouteConstraintAttributeValuesTest.cs new file mode 100644 index 0000000000..a184379dcf --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/RouteConstraintAttributeValuesTest.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 Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public class RouteConstraintAttributeValuesTest + { + [Fact] + public void RouteConstraintAttributeValues_IncludesAllProperties() + { + // Arrange + var exclude = new[] { "TypeId" }; + + // Assert + PropertiesAssert.PropertiesAreTheSame( + typeof(RouteConstraintAttribute), + typeof(RouteConstraintAttributeValues), + exclude); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/RouteDataActionConstraintValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/RouteDataActionConstraintValuesTest.cs new file mode 100644 index 0000000000..63b6d4311d --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/RouteDataActionConstraintValuesTest.cs @@ -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. + +using Xunit; + +namespace Microsoft.AspNet.Mvc.Logging +{ + public class RouteDataActionConstraintValuesTest + { + [Fact] + public void RouteDataActionConstraintValues_IncludesAllProperties() + { + // Assert + PropertiesAssert.PropertiesAreTheSame( + typeof(RouteDataActionConstraint), + typeof(RouteDataActionConstraintValues)); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/StaticControllerModelBuilder.cs b/test/Microsoft.AspNet.Mvc.Core.Test/StaticControllerModelBuilder.cs index 7b8af20b09..1ea00f6030 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/StaticControllerModelBuilder.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/StaticControllerModelBuilder.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public class StaticControllerModelBuilder : DefaultControllerModelBuilder { public StaticControllerModelBuilder(params TypeInfo[] controllerTypes) - : base(new DefaultActionModelBuilder()) + : base(new DefaultActionModelBuilder(), new NullLoggerFactory()) { ControllerTypes = new List(controllerTypes ?? Enumerable.Empty()); } diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs index 190d4df449..4fd2013829 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs @@ -12,6 +12,7 @@ using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.WebApiCompatShim; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.NestedProviders; +using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; using Moq; using Xunit; @@ -360,7 +361,8 @@ namespace System.Web.Http assemblyProvider.Object, new NamespaceLimitedActionDiscoveryConventions(), filterProvider.Object, - optionsAccessor.Object); + optionsAccessor.Object, + new NullLoggerFactory()); return new NestedProviderManager( new INestedProvider[] @@ -372,7 +374,7 @@ namespace System.Web.Http private class NamespaceLimitedActionDiscoveryConventions : DefaultControllerModelBuilder { public NamespaceLimitedActionDiscoveryConventions() - : base(new DefaultActionModelBuilder()) + : base(new DefaultActionModelBuilder(), new NullLoggerFactory()) { } From c6eaf00b0291102a2bf6d9bd5ff36fd30948f01f Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 19 Dec 2014 17:38:29 -0800 Subject: [PATCH 020/118] Reacting to FileSystem changes --- .../Compilation/CompilerCacheEntry.cs | 4 ++-- .../Razor/PreCompileViews/RazorFileInfo.cs | 2 +- .../Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs index bf41278741..ffbccc2d40 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs @@ -53,9 +53,9 @@ namespace Microsoft.AspNet.Mvc.Razor public long Length { get; private set; } /// - /// Gets or sets the last modified for the file at the time of compilation. + /// Gets or sets the last modified for the file at the time of compilation. /// - public DateTime LastModified { get; set; } + public DateTimeOffset LastModified { get; set; } /// /// Gets the file hash, should only be available for pre compiled files. diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfo.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfo.cs index 211970f045..e19113b0a1 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfo.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfo.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// Last modified at compilation time. /// - public DateTime LastModified { get; set; } + public DateTimeOffset LastModified { get; set; } /// /// The length of the file in bytes. diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs index 7d3b9e5dad..e7da94f301 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs @@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.Razor protected virtual string GenerateFile([NotNull] RazorFileInfo fileInfo) { return string.Format(FileFormat, - fileInfo.LastModified.ToFileTimeUtc(), + fileInfo.LastModified.ToFileTime(), fileInfo.Length, fileInfo.RelativePath, fileInfo.FullTypeName, From 9df8a1e73f0e3bfa083aa11a3b8f2d3502caab0d Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 19 Dec 2014 18:10:57 -0800 Subject: [PATCH 021/118] Reacting to FileSystem changes v2 --- test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs index 21e36ecde4..b561494fef 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.Razor public bool IsDirectory { get; } = false; - public DateTime LastModified { get; set; } + public DateTimeOffset LastModified { get; set; } public long Length { get; set; } From 6afd8710fa5d575e03229179c0cdb9bbe4a6fa01 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Mon, 15 Dec 2014 15:25:12 -0800 Subject: [PATCH 022/118] React to aspnet/Razor#207 changes. --- .../MvcRazorHostTest.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs index 210b1ed6bf..69593eed9e 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs @@ -51,7 +51,14 @@ namespace Microsoft.AspNet.Mvc.Razor generatedAbsoluteIndex: 823, generatedLineIndex: 25, generatedCharacterIndex: 14, - contentLength: 85) + contentLength: 85), + BuildLineMapping(documentAbsoluteIndex: 138, + documentLineIndex: 4, + documentCharacterIndex: 16, + generatedAbsoluteIndex: 2058, + generatedLineIndex: 52, + generatedCharacterIndex: 107, + contentLength: 3) }; // Act and Assert From a818240d8a1fd968199ed0269eec9b56bffd6407 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Tue, 23 Dec 2014 16:34:13 -0800 Subject: [PATCH 023/118] React to aspnet/Razor#263 changes --- src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs | 2 +- .../ValidationMessageTagHelper.cs | 2 +- .../ValidationSummaryTagHelper.cs | 2 +- .../TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs | 2 +- .../WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs | 2 +- .../TagHelpersWebSite/TagHelpers/NestedViewStartTagHelper.cs | 2 +- test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs | 2 +- .../TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs | 2 +- .../TagHelpers/TagCloudViewComponentTagHelper.cs | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs index f4d7f58211..2b8a21a792 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/AnchorTagHelper.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// /// implementation targeting <a> elements. /// - [TagName("a")] + [HtmlElementName("a")] public class AnchorTagHelper : TagHelper { private const string ActionAttributeName = "asp-action"; diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs index ef9ae6dd8e..0469660519 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationMessageTagHelper.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// implementation targeting <span> elements with an asp-validation-for /// attribute. /// - [TagName("span")] + [HtmlElementName("span")] [ContentBehavior(ContentBehavior.Modify)] public class ValidationMessageTagHelper : TagHelper { diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs index fb0fa1341c..6a97dba832 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// implementation targeting <div> elements with an asp-validation-summary /// attribute. /// - [TagName("div")] + [HtmlElementName("div")] [ContentBehavior(ContentBehavior.Append)] public class ValidationSummaryTagHelper : TagHelper { diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs index 0487a7b012..678b6fd591 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs @@ -7,7 +7,7 @@ using Microsoft.AspNet.Razor.TagHelpers; namespace TagHelpersWebSite.TagHelpers { - [TagName("p")] + [HtmlElementName("p")] [ContentBehavior(ContentBehavior.Modify)] public class AutoLinkerTagHelper : TagHelper { diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs index b9a0027c82..290c30a6c8 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs @@ -6,7 +6,7 @@ using Microsoft.AspNet.Razor.TagHelpers; namespace TagHelpersWebSite.TagHelpers { - [TagName("div", "style", "p")] + [HtmlElementName("div", "style", "p")] [ContentBehavior(ContentBehavior.Modify)] public class ConditionTagHelper : TagHelper { diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewStartTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewStartTagHelper.cs index 4dc77707c3..cd07dabb7e 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewStartTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewStartTagHelper.cs @@ -6,7 +6,7 @@ using Microsoft.AspNet.Razor.TagHelpers; namespace TagHelpersWebSite.TagHelpers { - [TagName("nested")] + [HtmlElementName("nested")] [ContentBehavior(ContentBehavior.Modify)] public class NestedViewStartTagHelper : TagHelper { diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs index c2076e445c..be22553fc7 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs @@ -8,7 +8,7 @@ using Microsoft.AspNet.Razor.Runtime.TagHelpers; namespace TagHelpersWebSite.TagHelpers { - [TagName("*")] + [HtmlElementName("*")] public class PrettyTagHelper : TagHelper { private static readonly Dictionary PrettyTagStyles = diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs index 18fdd2fed8..c25be05c1a 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs @@ -6,7 +6,7 @@ using Microsoft.AspNet.Razor.TagHelpers; namespace TagHelpersWebSite.TagHelpers { - [TagName("root")] + [HtmlElementName("root")] [ContentBehavior(ContentBehavior.Replace)] public class RootViewStartTagHelper : TagHelper { diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs index 0cf0de6cf7..4bfcdea267 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs @@ -12,7 +12,7 @@ using Microsoft.AspNet.Razor.TagHelpers; namespace MvcSample.Web.Components { - [TagName("tag-cloud")] + [HtmlElementName("tag-cloud")] [ViewComponent(Name = "Tags")] [ContentBehavior(ContentBehavior.Replace)] public class TagCloudViewComponentTagHelper : ITagHelper From db961049851be53e9137c485fd4589f609ee4af9 Mon Sep 17 00:00:00 2001 From: riande Date: Tue, 23 Dec 2014 15:37:35 -0800 Subject: [PATCH 024/118] removed App_data per [Mvc] Remove App_Data from MvcSample.Web Startup (#1716) --- samples/MvcSample.Web/Startup.cs | 6 +++--- samples/MvcSample.Web/{App_Data/Config.json => config.json} | 0 2 files changed, 3 insertions(+), 3 deletions(-) rename samples/MvcSample.Web/{App_Data/Config.json => config.json} (100%) diff --git a/samples/MvcSample.Web/Startup.cs b/samples/MvcSample.Web/Startup.cs index 96d203d451..20098d157a 100644 --- a/samples/MvcSample.Web/Startup.cs +++ b/samples/MvcSample.Web/Startup.cs @@ -25,10 +25,10 @@ namespace MvcSample.Web { app.UseFileServer(); #if ASPNET50 - // We use Path.Combine here so that it works on platforms other than Windows as well. + // Set up configuration sources. var configuration = new Configuration() - .AddJsonFile(Path.Combine("App_Data", "config.json")) - .AddEnvironmentVariables(); + .AddJsonFile("config.json") + .AddEnvironmentVariables(); string diSystem; if (configuration.TryGet("DependencyInjection", out diSystem) && diff --git a/samples/MvcSample.Web/App_Data/Config.json b/samples/MvcSample.Web/config.json similarity index 100% rename from samples/MvcSample.Web/App_Data/Config.json rename to samples/MvcSample.Web/config.json From 5dfd27e51fd43c919951b357c64143039bbcd086 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 31 Dec 2014 11:26:49 -0800 Subject: [PATCH 025/118] Removing dead code in sample --- samples/MvcSample.Web/Home2Controller.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/samples/MvcSample.Web/Home2Controller.cs b/samples/MvcSample.Web/Home2Controller.cs index 93f0ba7c6f..f9843c0984 100644 --- a/samples/MvcSample.Web/Home2Controller.cs +++ b/samples/MvcSample.Web/Home2Controller.cs @@ -18,8 +18,6 @@ namespace MvcSample.Web.RandomNameSpace get; set; } - public ActionContext ActionContext { get; set; } - public string Index() { return "Hello World: my namespace is " + this.GetType().Namespace; From ae9fc793ece34b831bb2e2ae7fc073eaa8f66d7f Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 25 Nov 2014 10:45:14 -0800 Subject: [PATCH 026/118] Adding IRouteConstraintProvider and supporting it on actions This change adds an interface for the functionality provide by RouteConstraintAttribute, and adds support for configuration constraints on actions/action-model. --- .../ApplicationModels/ActionModel.cs | 6 +- .../ApplicationModels/AttributeRouteModel.cs | 3 +- .../ApplicationModels/ControllerModel.cs | 6 +- .../DefaultActionModelBuilder.cs | 2 + .../DefaultControllerModelBuilder.cs | 4 +- .../ControllerActionDescriptorBuilder.cs | 85 ++++++++++++------- .../IRouteConstraintProvider.cs | 33 +++++++ .../Logging/ActionModelValues.cs | 8 ++ .../Logging/ControllerModelValues.cs | 6 +- ...es.cs => RouteConstraintProviderValues.cs} | 16 ++-- .../RouteConstraintAttribute.cs | 21 ++--- .../ApplicationModel/ActionModelTest.cs | 1 + .../AttributeRouteModelTests.cs | 2 +- ...ControllerActionDescriptorProviderTests.cs | 2 +- ...s => RouteConstraintProviderValuesTest.cs} | 6 +- 15 files changed, 131 insertions(+), 70 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/IRouteConstraintProvider.cs rename src/Microsoft.AspNet.Mvc.Core/Logging/{RouteConstraintAttributeValues.cs => RouteConstraintProviderValues.cs} (64%) rename test/Microsoft.AspNet.Mvc.Core.Test/{RouteConstraintAttributeValuesTest.cs => RouteConstraintProviderValuesTest.cs} (73%) diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs index 077b868482..04322a9c27 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs @@ -20,6 +20,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Filters = new List(); HttpMethods = new List(); Parameters = new List(); + RouteConstraints = new List(); } public ActionModel([NotNull] ActionModel other) @@ -40,6 +41,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels // Make a deep copy of other 'model' types. ApiExplorer = new ApiExplorerModel(other.ApiExplorer); Parameters = new List(other.Parameters.Select(p => new ParameterModel(p))); + RouteConstraints = new List(other.RouteConstraints); if (other.AttributeRouteModel != null) { @@ -62,6 +64,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels /// public ApiExplorerModel ApiExplorer { get; set; } + public AttributeRouteModel AttributeRouteModel { get; set; } + public IReadOnlyList Attributes { get; } public ControllerModel Controller { get; set; } @@ -74,6 +78,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public List Parameters { get; private set; } - public AttributeRouteModel AttributeRouteModel { get; set; } + public List RouteConstraints { get; private set; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs index 1130ecac2c..199ad7f8a2 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text; using Microsoft.AspNet.Mvc.Core; using Microsoft.AspNet.Mvc.Routing; @@ -336,7 +337,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels var message = Resources.FormatAttributeRoute_TokenReplacement_ReplacementValueNotFound( template, token, - string.Join(", ", values.Keys)); + string.Join(", ", values.Keys.OrderBy(k => k, StringComparer.OrdinalIgnoreCase))); throw new InvalidOperationException(message); } diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ControllerModel.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ControllerModel.cs index 247a8b1108..9b5ee6c2dd 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ControllerModel.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ControllerModel.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels AttributeRoutes = new List(); ActionConstraints = new List(); Filters = new List(); - RouteConstraints = new List(); + RouteConstraints = new List(); } public ControllerModel([NotNull] ControllerModel other) @@ -35,7 +35,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels ActionConstraints = new List(other.ActionConstraints); Attributes = new List(other.Attributes); Filters = new List(other.Filters); - RouteConstraints = new List(other.RouteConstraints); + RouteConstraints = new List(other.RouteConstraints); // Make a deep copy of other 'model' types. Actions = new List(other.Actions.Select(a => new ActionModel(a))); @@ -65,6 +65,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public List Filters { get; private set; } - public List RouteConstraints { get; private set; } + public List RouteConstraints { get; private set; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs index eb6c11ab56..5115940742 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs @@ -289,6 +289,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels .SelectMany(a => a.HttpMethods) .Distinct()); + actionModel.RouteConstraints.AddRange(attributes.OfType()); + var routeTemplateProvider = attributes .OfType() diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs index 0e1dc06014..b8fa51f4d5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs @@ -122,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels controllerModel.ActionConstraints.AddRange(attributes.OfType()); controllerModel.Filters.AddRange(attributes.OfType()); - controllerModel.RouteConstraints.AddRange(attributes.OfType()); + controllerModel.RouteConstraints.AddRange(attributes.OfType()); controllerModel.AttributeRoutes.AddRange( attributes.OfType().Select(rtp => new AttributeRouteModel(rtp))); @@ -142,4 +142,4 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels return controllerModel; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs index 0cff9910c2..aff348f9fa 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs @@ -56,11 +56,7 @@ namespace Microsoft.AspNet.Mvc actionDescriptor.ControllerTypeInfo = controller.ControllerType; AddApiExplorerInfo(actionDescriptor, action, controller); - AddRouteConstraints(actionDescriptor, controller, action); - AddControllerRouteConstraints( - actionDescriptor, - controller.RouteConstraints, - removalConstraints); + AddRouteConstraints(removalConstraints, actionDescriptor, controller, action); if (IsAttributeRoutedAction(actionDescriptor)) { @@ -371,39 +367,17 @@ namespace Microsoft.AspNet.Mvc } public static void AddRouteConstraints( + ISet removalConstraints, ControllerActionDescriptor actionDescriptor, ControllerModel controller, ActionModel action) { - actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( - "controller", - controller.ControllerName)); - - if (action.IsActionNameMatchRequired) - { - actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( - "action", - action.ActionName)); - } - else - { - actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( - "action", - string.Empty)); - } - } - - private static void AddControllerRouteConstraints( - ControllerActionDescriptor actionDescriptor, - IList routeconstraints, - ISet removalConstraints) - { - // Apply all the constraints defined on the controller (for example, [Area]) to the actions - // in that controller. Also keep track of all the constraints that require preventing actions + // Apply all the constraints defined on the action, then controller (for example, [Area]) + // to the actions. Also keep track of all the constraints that require preventing actions // without the constraint to match. For example, actions without an [Area] attribute on their // controller should not match when a value has been given for area when matching a url or // generating a link. - foreach (var constraintAttribute in routeconstraints) + foreach (var constraintAttribute in action.RouteConstraints) { if (constraintAttribute.BlockNonAttributedActions) { @@ -427,6 +401,55 @@ 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")) + { + if (action.IsActionNameMatchRequired) + { + actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( + "action", + action.ActionName)); + } + else + { + actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( + "action", + string.Empty)); + } + } + + if (!HasConstraint(actionDescriptor.RouteConstraints, "controller")) + { + actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( + "controller", + controller.ControllerName)); + } } private static bool HasConstraint(List constraints, string routeKey) diff --git a/src/Microsoft.AspNet.Mvc.Core/IRouteConstraintProvider.cs b/src/Microsoft.AspNet.Mvc.Core/IRouteConstraintProvider.cs new file mode 100644 index 0000000000..4a72ce23f4 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/IRouteConstraintProvider.cs @@ -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. + +namespace Microsoft.AspNet.Mvc +{ + /// + /// An interface for metadata which provides values + /// for a controller or action. + /// + public interface IRouteConstraintProvider + { + /// + /// The route value key. + /// + string RouteKey { get; } + + /// + /// The . + /// + RouteKeyHandling RouteKeyHandling { get; } + + /// + /// The expected route value. Will be null unless is + /// set to . + /// + string RouteValue { get; } + + /// + /// Set to true to negate this constraint on all actions that do not define a behavior for this route key. + /// + bool BlockNonAttributedActions { get; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs index a4c5344f01..661a2cab65 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs @@ -24,6 +24,8 @@ namespace Microsoft.AspNet.Mvc.Logging ActionMethod = inner.ActionMethod; ApiExplorer = new ApiExplorerModelValues(inner.ApiExplorer); Parameters = inner.Parameters.Select(p => new ParameterModelValues(p)).ToList(); + RouteConstraints = inner.RouteConstraints.Select( + r => new RouteConstraintProviderValues(r)).ToList(); Filters = inner.Filters.Select(f => new FilterValues(f)).ToList(); if (inner.AttributeRouteModel != null) { @@ -60,6 +62,12 @@ namespace Microsoft.AspNet.Mvc.Logging /// See . /// public List Filters { get; } + + /// + /// The route constraints on the controller as . + /// See . + /// + public List RouteConstraints { get; set; } /// /// The attribute route model of the action as . diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs index 7682934bb3..3ca2d23014 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.Logging Filters = inner.Filters.Select(f => new FilterValues(f)).ToList(); ActionConstraints = inner.ActionConstraints?.Select(a => new ActionConstraintValues(a))?.ToList(); RouteConstraints = inner.RouteConstraints.Select( - r => new RouteConstraintAttributeValues(r)).ToList(); + r => new RouteConstraintProviderValues(r)).ToList(); AttributeRoutes = inner.AttributeRoutes.Select( a => new AttributeRouteModelValues(a)).ToList(); } @@ -72,10 +72,10 @@ namespace Microsoft.AspNet.Mvc.Logging public List ActionConstraints { get; } /// - /// The route constraints on the controller as . + /// The route constraints on the controller as . /// See . /// - public List RouteConstraints { get; set; } + public List RouteConstraints { get; set; } /// /// The attribute routes on the controller as . diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/RouteConstraintAttributeValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/RouteConstraintProviderValues.cs similarity index 64% rename from src/Microsoft.AspNet.Mvc.Core/Logging/RouteConstraintAttributeValues.cs rename to src/Microsoft.AspNet.Mvc.Core/Logging/RouteConstraintProviderValues.cs index 75638d97b8..35c6a2c218 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/RouteConstraintAttributeValues.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/RouteConstraintProviderValues.cs @@ -6,12 +6,12 @@ using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc.Logging { /// - /// Logging representation of a . Logged as a substructure of + /// Logging representation of a . Logged as a substructure of /// /// - public class RouteConstraintAttributeValues : LoggerStructureBase + public class RouteConstraintProviderValues : LoggerStructureBase { - public RouteConstraintAttributeValues([NotNull] RouteConstraintAttribute inner) + public RouteConstraintProviderValues([NotNull] IRouteConstraintProvider inner) { RouteKey = inner.RouteKey; RouteValue = inner.RouteValue; @@ -20,22 +20,22 @@ namespace Microsoft.AspNet.Mvc.Logging } /// - /// The route value key. See . + /// The route value key. See . /// public string RouteKey { get; } /// - /// The expected route value. See . + /// The expected route value. See . /// public string RouteValue { get; } /// - /// The . See . + /// The . See . /// public RouteKeyHandling RouteKeyHandling { get; } /// - /// See . + /// See . /// public bool BlockNonAttributedActions { get; } @@ -44,4 +44,4 @@ namespace Microsoft.AspNet.Mvc.Logging return LogFormatter.FormatStructure(this); } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNet.Mvc.Core/RouteConstraintAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/RouteConstraintAttribute.cs index 99bd6dd016..3fad1d8553 100644 --- a/src/Microsoft.AspNet.Mvc.Core/RouteConstraintAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/RouteConstraintAttribute.cs @@ -15,11 +15,9 @@ namespace Microsoft.AspNet.Mvc /// /// When placed on a controller, unless overridden by the action, the constraint applies to all /// actions defined by the controller. - /// - /// /// [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)] - public abstract class RouteConstraintAttribute : Attribute + public abstract class RouteConstraintAttribute : Attribute, IRouteConstraintProvider { /// /// Creates a new . @@ -65,25 +63,16 @@ namespace Microsoft.AspNet.Mvc BlockNonAttributedActions = blockNonAttributedActions; } - /// - /// The route value key. - /// + /// public string RouteKey { get; private set; } - /// - /// The . - /// + /// public RouteKeyHandling RouteKeyHandling { get; private set; } - /// - /// The expected route value. Will be null unless is - /// set to . - /// + /// public string RouteValue { get; private set; } - /// - /// Set to true to negate this constraint on all actions that do not define a behavior for this route key. - /// + /// public bool BlockNonAttributedActions { get; private set; } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs index bfdfc80290..3bafa0d5b9 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs @@ -53,6 +53,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels action.Filters.Add(new AuthorizeAttribute()); action.HttpMethods.Add("GET"); action.IsActionNameMatchRequired = true; + action.RouteConstraints.Add(new AreaAttribute("Admin")); // Act var action2 = new ActionModel(action); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/AttributeRouteModelTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/AttributeRouteModelTests.cs index 233155c919..85f179b53a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/AttributeRouteModelTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/AttributeRouteModelTests.cs @@ -200,7 +200,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels var expected = "While processing template '[area]/[controller]/[action2]', " + "a replacement value for the token 'action2' could not be found. " + - "Available tokens: 'area, controller, action'."; + "Available tokens: 'action, area, controller'."; // Act var ex = Assert.Throws( diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs index 6e9034a301..8e151af5ec 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs @@ -547,7 +547,7 @@ namespace Microsoft.AspNet.Mvc.Test "For action: 'Microsoft.AspNet.Mvc.Test.ControllerActionDescriptorProviderTests+" + "MultipleErrorsController.Unknown'" + Environment.NewLine + "Error: While processing template 'stub/[action]/[unknown]', a replacement value for the token 'unknown' " + - "could not be found. Available tokens: 'controller, action'." + Environment.NewLine + + "could not be found. Available tokens: 'action, controller'." + Environment.NewLine + Environment.NewLine + "Error 2:" + Environment.NewLine + "For action: 'Microsoft.AspNet.Mvc.Test.ControllerActionDescriptorProviderTests+" + diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/RouteConstraintAttributeValuesTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/RouteConstraintProviderValuesTest.cs similarity index 73% rename from test/Microsoft.AspNet.Mvc.Core.Test/RouteConstraintAttributeValuesTest.cs rename to test/Microsoft.AspNet.Mvc.Core.Test/RouteConstraintProviderValuesTest.cs index a184379dcf..a0c28a100c 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/RouteConstraintAttributeValuesTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/RouteConstraintProviderValuesTest.cs @@ -5,10 +5,10 @@ using Xunit; namespace Microsoft.AspNet.Mvc.Logging { - public class RouteConstraintAttributeValuesTest + public class RouteConstraintProviderValuesTest { [Fact] - public void RouteConstraintAttributeValues_IncludesAllProperties() + public void RouteConstraintProviderValues_IncludesAllProperties() { // Arrange var exclude = new[] { "TypeId" }; @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.Logging // Assert PropertiesAssert.PropertiesAreTheSame( typeof(RouteConstraintAttribute), - typeof(RouteConstraintAttributeValues), + typeof(RouteConstraintProviderValues), exclude); } } From e2a4b1ec7281c16da3d2573a03927ec7718ac75a Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 20 Nov 2014 18:17:09 -0800 Subject: [PATCH 027/118] Remove IsActionNameMatchRequired This change removes IsActionNameMatchRequired from the action model. The WebAPI shim uses a custom route data constraint to get the same effect. --- .../ApplicationModels/ActionModel.cs | 3 --- .../DefaultActionModelBuilder.cs | 5 +---- .../ControllerActionDescriptorBuilder.cs | 15 +++---------- ...onConventionsApplicationModelConvention.cs | 22 +++++++++++++++++-- .../ApplicationModel/ActionModelTest.cs | 1 - .../DefaultActionModelBuilderTest.cs | 13 ----------- ...ControllerActionDescriptorProviderTests.cs | 2 -- 7 files changed, 24 insertions(+), 37 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs index 04322a9c27..c1ea508f90 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs @@ -27,7 +27,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels { ActionMethod = other.ActionMethod; ActionName = other.ActionName; - IsActionNameMatchRequired = other.IsActionNameMatchRequired; // Not making a deep copy of the controller, this action still belongs to the same controller. Controller = other.Controller; @@ -74,8 +73,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public List HttpMethods { get; private set; } - public bool IsActionNameMatchRequired { get; set; } - public List Parameters { get; private set; } public List RouteConstraints { get; private set; } diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs index 5115940742..4bc8b26271 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs @@ -252,10 +252,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels [NotNull] MethodInfo methodInfo, [NotNull] IReadOnlyList attributes) { - var actionModel = new ActionModel(methodInfo, attributes) - { - IsActionNameMatchRequired = true, - }; + var actionModel = new ActionModel(methodInfo, attributes); actionModel.ActionConstraints.AddRange(attributes.OfType()); actionModel.Filters.AddRange(attributes.OfType()); diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs index aff348f9fa..27c0536bff 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs @@ -430,18 +430,9 @@ namespace Microsoft.AspNet.Mvc // Lastly add the 'default' values if (!HasConstraint(actionDescriptor.RouteConstraints, "action")) { - if (action.IsActionNameMatchRequired) - { - actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( - "action", - action.ActionName)); - } - else - { - actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( - "action", - string.Empty)); - } + actionDescriptor.RouteConstraints.Add(new RouteDataActionConstraint( + "action", + action.ActionName ?? string.Empty)); } if (!HasConstraint(actionDescriptor.RouteConstraints, "controller")) diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs index 159ab8f3b4..bbf47089eb 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs @@ -52,7 +52,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim var namedAction = action; var unnamedAction = new ActionModel(namedAction); - unnamedAction.IsActionNameMatchRequired = false; + unnamedAction.RouteConstraints.Add(new UnnamedActionRouteConstraint()); newActions.Add(unnamedAction); } } @@ -91,5 +91,23 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim // If no convention matches, then assume POST action.HttpMethods.Add("POST"); } + + private class UnnamedActionRouteConstraint : IRouteConstraintProvider + { + public UnnamedActionRouteConstraint() + { + RouteKey = "action"; + RouteKeyHandling = RouteKeyHandling.DenyKey; + RouteValue = null; + } + + public string RouteKey { get; } + + public RouteKeyHandling RouteKeyHandling { get; } + + public string RouteValue { get; } + + public bool BlockNonAttributedActions { get; } + } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs index 3bafa0d5b9..766c69f008 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs @@ -52,7 +52,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels new List()); action.Filters.Add(new AuthorizeAttribute()); action.HttpMethods.Add("GET"); - action.IsActionNameMatchRequired = true; action.RouteConstraints.Add(new AreaAttribute("Admin")); // Act diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs index b4b2a371d4..ee81a9b625 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs @@ -294,7 +294,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels // Assert var action = Assert.Single(actions); Assert.Equal("Edit", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); Assert.Empty(action.HttpMethods); Assert.Null(action.AttributeRouteModel); Assert.Empty(action.Attributes); @@ -317,7 +316,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Assert.Contains("PATCH", action.HttpMethods); Assert.Equal("Update", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); Assert.Null(action.AttributeRouteModel); Assert.IsType(Assert.Single(action.Attributes)); } @@ -336,7 +334,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels // Assert var action = Assert.Single(actions); Assert.Equal("Delete", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); var httpMethod = Assert.Single(action.HttpMethods); Assert.Equal("DELETE", httpMethod); @@ -361,7 +358,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Assert.Contains("GET", action.HttpMethods); Assert.Contains("POST", action.HttpMethods); Assert.Equal("Details", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); Assert.Null(action.AttributeRouteModel); } @@ -382,7 +378,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Assert.Contains("PUT", action.HttpMethods); Assert.Contains("POST", action.HttpMethods); Assert.Equal("List", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); Assert.Null(action.AttributeRouteModel); } @@ -401,7 +396,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels var action = Assert.Single(actions); Assert.Equal("Edit", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); var httpMethod = Assert.Single(action.HttpMethods); Assert.Equal("POST", httpMethod); @@ -427,7 +421,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels var action = Assert.Single(actions); Assert.Equal("Update", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); Assert.Empty(action.HttpMethods); @@ -452,7 +445,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels var action = Assert.Single(actions); Assert.Equal("List", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); Assert.Equal(new[] { "GET", "HEAD" }, action.HttpMethods.OrderBy(m => m, StringComparer.Ordinal)); @@ -479,8 +471,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels foreach (var action in actions) { Assert.Equal("Index", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); - Assert.NotNull(action.AttributeRouteModel); } @@ -510,7 +500,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels var action = Assert.Single(actions); Assert.Equal("Remove", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); Assert.Empty(action.HttpMethods); @@ -535,7 +524,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels var action = Assert.Single(actions); Assert.Equal("Delete", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); Assert.Empty(action.HttpMethods); @@ -562,7 +550,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels foreach (var action in actions) { Assert.Equal("Index", action.ActionName); - Assert.True(action.IsActionNameMatchRequired); var httpMethod = Assert.Single(action.HttpMethods); Assert.Equal("GET", httpMethod); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs index 8e151af5ec..e461393b17 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs @@ -475,11 +475,9 @@ namespace Microsoft.AspNet.Mvc.Test var getPerson = Assert.Single(controller.Actions, a => a.ActionName == "GetPerson"); Assert.Empty(getPerson.HttpMethods); - Assert.True(getPerson.IsActionNameMatchRequired); var showPeople = Assert.Single(controller.Actions, a => a.ActionName == "ShowPeople"); Assert.Empty(showPeople.HttpMethods); - Assert.True(showPeople.IsActionNameMatchRequired); } [Fact] From 62b9db93b66cac02acd299af786d8cfa7b11fc87 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 2 Jan 2015 14:34:57 -0800 Subject: [PATCH 028/118] Removing unused code from UnclassifiedCodeSpanConstructor to fix build --- .../UnclassifiedCodeSpanConstructor.cs | 59 +------------------ 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/UnclassifiedCodeSpanConstructor.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/UnclassifiedCodeSpanConstructor.cs index f6e05b8c34..37c633a9e5 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/UnclassifiedCodeSpanConstructor.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/SpanFactory/UnclassifiedCodeSpanConstructor.cs @@ -1,76 +1,19 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; -using Microsoft.AspNet.Razor.Editor; using Microsoft.AspNet.Razor.Generator; -using Microsoft.AspNet.Razor.Parser.SyntaxTree; namespace Microsoft.AspNet.Mvc.Razor { public class UnclassifiedCodeSpanConstructor { - private SpanConstructor _self; + private readonly SpanConstructor _self; public UnclassifiedCodeSpanConstructor(SpanConstructor self) { _self = self; } - public SpanConstructor AsMetaCode() - { - _self.Builder.Kind = SpanKind.MetaCode; - return _self; - } - - public SpanConstructor AsStatement() - { - return _self.With(new StatementCodeGenerator()); - } - - public SpanConstructor AsExpression() - { - return _self.With(new ExpressionCodeGenerator()); - } - - public SpanConstructor AsImplicitExpression(ISet keywords) - { - return AsImplicitExpression(keywords, acceptTrailingDot: false); - } - - public SpanConstructor AsImplicitExpression(ISet keywords, bool acceptTrailingDot) - { - return _self.With(new ImplicitExpressionEditHandler(SpanConstructor.TestTokenizer, - keywords, - acceptTrailingDot)) - .With(new ExpressionCodeGenerator()); - } - - public SpanConstructor AsFunctionsBody() - { - return _self.With(new TypeMemberCodeGenerator()); - } - - public SpanConstructor AsNamespaceImport(string ns, int namespaceKeywordLength) - { - return _self.With(new AddImportCodeGenerator(ns, namespaceKeywordLength)); - } - - public SpanConstructor Hidden() - { - return _self.With(SpanCodeGenerator.Null); - } - - public SpanConstructor AsBaseType(string baseType) - { - return _self.With(new SetBaseTypeCodeGenerator(baseType)); - } - - public SpanConstructor AsRazorDirectiveAttribute(string key, string value) - { - return _self.With(new RazorDirectiveAttributeCodeGenerator(key, value)); - } - public SpanConstructor As(ISpanCodeGenerator codeGenerator) { return _self.With(codeGenerator); From 110ee28e3e847fb096884ba66fabca295d8d545b Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 4 Dec 2014 19:18:26 -0800 Subject: [PATCH 029/118] Adding support for flowing compilation settings to views Fixes #871 --- .../CompilationOptionsProviderExtension.cs | 31 ++++ .../Compilation/RoslynCompilationService.cs | 19 +- .../Compilation/SyntaxTreeGenerator.cs | 33 +--- .../RazorFileInfoCollectionGenerator.cs | 10 +- .../Razor/PreCompileViews/RazorPreCompiler.cs | 29 +-- src/Microsoft.AspNet.Mvc.Razor/project.json | 2 +- src/Microsoft.AspNet.Mvc/MvcServices.cs | 1 - .../RazorPreCompileModule.cs | 9 +- .../CompilationOptionsTests.cs | 46 +++++ .../PrecompilationTest.cs | 31 +++- .../RazorCompilationServiceTest.cs | 0 .../RoslynCompilationServiceTest.cs | 170 ++++++++++++++++++ .../Controllers/HomeController.cs | 5 + .../Index.cshtml | 9 + .../PrecompilationWebSite/project.json | 12 +- ...wsConsumingCompilationOptionsController.cs | 17 ++ .../Services/FrameworkSpecificHelper.cs | 31 ++++ test/WebSites/RazorWebSite/Startup.cs | 1 + .../Index.cshtml | 3 + .../_Partial.cshtml | 11 ++ test/WebSites/RazorWebSite/project.json | 12 +- 21 files changed, 420 insertions(+), 62 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationOptionsProviderExtension.cs create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/CompilationOptionsTests.cs rename test/Microsoft.AspNet.Mvc.Razor.Test/{ => Compilation}/RazorCompilationServiceTest.cs (100%) create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs create mode 100644 test/WebSites/PrecompilationWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml create mode 100644 test/WebSites/RazorWebSite/Controllers/ViewsConsumingCompilationOptionsController.cs create mode 100644 test/WebSites/RazorWebSite/Services/FrameworkSpecificHelper.cs create mode 100644 test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/_Partial.cshtml diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationOptionsProviderExtension.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationOptionsProviderExtension.cs new file mode 100644 index 0000000000..4e0ccc6179 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationOptionsProviderExtension.cs @@ -0,0 +1,31 @@ +// 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.Runtime; +using Microsoft.Framework.Runtime.Roslyn; + +namespace Microsoft.AspNet.Mvc.Razor +{ + /// + /// Extension methods for . + /// + public static class CompilationOptionsProviderExtension + { + /// + /// Parses the for the current executing application and returns a + /// used for Roslyn compilation. + /// + /// A that reads compiler options. + /// The for the executing application. + /// The for the current application. + public static CompilationSettings GetCompilationSettings( + [NotNull] this ICompilerOptionsProvider compilerOptionsProvider, + [NotNull] IApplicationEnvironment applicationEnvironment) + { + return compilerOptionsProvider.GetCompilerOptions(applicationEnvironment.ApplicationBasePath, + applicationEnvironment.RuntimeFramework, + applicationEnvironment.Configuration) + .ToCompilationSettings(applicationEnvironment.RuntimeFramework); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs index a0a977e037..047ce5acb3 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs @@ -15,7 +15,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; using Microsoft.Framework.Runtime; -namespace Microsoft.AspNet.Mvc.Razor.Compilation +namespace Microsoft.AspNet.Mvc.Razor { /// /// A type that uses Roslyn to compile C# content. @@ -29,6 +29,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation private readonly ILibraryManager _libraryManager; private readonly IApplicationEnvironment _environment; private readonly IAssemblyLoadContext _loader; + private readonly ICompilerOptionsProvider _compilerOptionsProvider; private readonly Lazy> _applicationReferences; @@ -44,12 +45,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation public RoslynCompilationService(IApplicationEnvironment environment, IAssemblyLoadContextAccessor loaderAccessor, ILibraryManager libraryManager, + ICompilerOptionsProvider compilerOptionsProvider, IMvcRazorHost host) { _environment = environment; _loader = loaderAccessor.GetLoadContext(typeof(RoslynCompilationService).GetTypeInfo().Assembly); _libraryManager = libraryManager; _applicationReferences = new Lazy>(GetApplicationReferences); + _compilerOptionsProvider = compilerOptionsProvider; _classPrefix = host.MainClassNamePrefix; } @@ -60,15 +63,19 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation // map to the source file. If a file does not exist on a physical file system, PhysicalPath will be null. // This prevents files that exist in a non-physical file system from being debugged. var path = fileInfo.PhysicalPath ?? fileInfo.Name; - var syntaxTrees = new[] { SyntaxTreeGenerator.Generate(compilationContent, path) }; - + var compilationSettings = _compilerOptionsProvider.GetCompilationSettings(_environment); + var syntaxTree = SyntaxTreeGenerator.Generate(compilationContent, + path, + compilationSettings); var references = _applicationReferences.Value; var assemblyName = Path.GetRandomFileName(); + var compilationOptions = compilationSettings.CompilationOptions + .WithOutputKind(OutputKind.DynamicallyLinkedLibrary); var compilation = CSharpCompilation.Create(assemblyName, - options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary), - syntaxTrees: syntaxTrees, + options: compilationOptions, + syntaxTrees: new[] { syntaxTree }, references: references); using (var ms = new MemoryStream()) @@ -115,7 +122,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Compilation .First(t => t.Name. StartsWith(_classPrefix, StringComparison.Ordinal)); - return UncachedCompilationResult.Successful(type); + return UncachedCompilationResult.Successful(type, compilationContent); } } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/SyntaxTreeGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/SyntaxTreeGenerator.cs index 0598a1a90e..0453ba051d 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/SyntaxTreeGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/SyntaxTreeGenerator.cs @@ -5,48 +5,29 @@ using System.Text; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; +using Microsoft.Framework.Runtime.Roslyn; namespace Microsoft.AspNet.Mvc.Razor { public static class SyntaxTreeGenerator { - private static CSharpParseOptions DefaultOptions - { - get - { - return CSharpParseOptions.Default - .WithLanguageVersion(LanguageVersion.CSharp6); - } - } - - public static SyntaxTree Generate([NotNull] string text, [NotNull] string path) - { - return GenerateCore(text, path, DefaultOptions); - } - public static SyntaxTree Generate([NotNull] string text, [NotNull] string path, - [NotNull] CSharpParseOptions options) - { - return GenerateCore(text, path, options); - } - - public static SyntaxTree GenerateCore([NotNull] string text, - [NotNull] string path, - [NotNull] CSharpParseOptions options) + [NotNull] CompilationSettings compilationSettings) { var sourceText = SourceText.From(text, Encoding.UTF8); var syntaxTree = CSharpSyntaxTree.ParseText(sourceText, path: path, - options: options); + options: GetParseOptions(compilationSettings)); return syntaxTree; } - public static CSharpParseOptions GetParseOptions(CSharpCompilation compilation) + public static CSharpParseOptions GetParseOptions(CompilationSettings compilationSettings) { - return CSharpParseOptions.Default - .WithLanguageVersion(compilation.LanguageVersion); + return new CSharpParseOptions( + languageVersion: compilationSettings.LanguageVersion, + preprocessorSymbols: compilationSettings.Defines.AsImmutable()); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs index e7da94f301..956aa62d63 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using System.Text; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Framework.Runtime.Roslyn; namespace Microsoft.AspNet.Mvc.Razor { @@ -13,13 +13,13 @@ namespace Microsoft.AspNet.Mvc.Razor private string _fileFormat; protected IReadOnlyList FileInfos { get; private set; } - protected CSharpParseOptions Options { get; private set; } + protected CompilationSettings CompilationSettings { get; } public RazorFileInfoCollectionGenerator([NotNull] IReadOnlyList fileInfos, - [NotNull] CSharpParseOptions options) + [NotNull] CompilationSettings compilationSettings) { FileInfos = fileInfos; - Options = options; + CompilationSettings = compilationSettings; } public virtual SyntaxTree GenerateCollection() @@ -38,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.Razor var sourceCode = builder.ToString(); var syntaxTree = SyntaxTreeGenerator.Generate(sourceCode, "__AUTO__GeneratedViewsCollection.cs", - Options); + CompilationSettings); return syntaxTree; } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs index 2d69ef1ff6..b41c3bf017 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs @@ -10,6 +10,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.OptionsModel; using Microsoft.Framework.Runtime; +using Microsoft.Framework.Runtime.Roslyn; namespace Microsoft.AspNet.Mvc.Razor { @@ -19,24 +20,28 @@ namespace Microsoft.AspNet.Mvc.Razor private readonly IFileSystem _fileSystem; private readonly IMvcRazorHost _host; - public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider) : + public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider, + [NotNull] CompilationSettings compilationSettings) : this(designTimeServiceProvider, designTimeServiceProvider.GetRequiredService(), - designTimeServiceProvider.GetRequiredService>()) + designTimeServiceProvider.GetRequiredService>(), + compilationSettings) { } public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider, [NotNull] IMvcRazorHost host, - [NotNull] IOptions optionsAccessor) + [NotNull] IOptions optionsAccessor, + [NotNull] CompilationSettings compilationSettings) { _serviceProvider = designTimeServiceProvider; _host = host; - - var appEnv = _serviceProvider.GetRequiredService(); _fileSystem = optionsAccessor.Options.FileSystem; + CompilationSettings = compilationSettings; } + protected CompilationSettings CompilationSettings { get; } + protected virtual string FileExtension { get; } = ".cshtml"; public virtual void CompileViews([NotNull] IBeforeCompileContext context) @@ -47,7 +52,7 @@ namespace Microsoft.AspNet.Mvc.Razor { var collectionGenerator = new RazorFileInfoCollectionGenerator( descriptors, - SyntaxTreeGenerator.GetParseOptions(context.CSharpCompilation)); + CompilationSettings); var tree = collectionGenerator.GenerateCollection(); context.CSharpCompilation = context.CSharpCompilation.AddSyntaxTrees(tree); @@ -57,14 +62,11 @@ namespace Microsoft.AspNet.Mvc.Razor protected virtual IReadOnlyList CreateCompilationDescriptors( [NotNull] IBeforeCompileContext context) { - var options = SyntaxTreeGenerator.GetParseOptions(context.CSharpCompilation); var list = new List(); foreach (var info in GetFileInfosRecursive(string.Empty)) { - var descriptor = ParseView(info, - context, - options); + var descriptor = ParseView(info, context); if (descriptor != null) { @@ -107,8 +109,7 @@ namespace Microsoft.AspNet.Mvc.Razor } protected virtual RazorFileInfo ParseView([NotNull] RelativeFileInfo fileInfo, - [NotNull] IBeforeCompileContext context, - [NotNull] CSharpParseOptions options) + [NotNull] IBeforeCompileContext context) { using (var stream = fileInfo.FileInfo.CreateReadStream()) { @@ -124,7 +125,9 @@ namespace Microsoft.AspNet.Mvc.Razor if (generatedCode != null) { - var syntaxTree = SyntaxTreeGenerator.Generate(generatedCode, fileInfo.FileInfo.PhysicalPath, options); + var syntaxTree = SyntaxTreeGenerator.Generate(generatedCode, + fileInfo.FileInfo.PhysicalPath, + CompilationSettings); var fullTypeName = results.GetMainClassName(_host, syntaxTree); if (fullTypeName != null) diff --git a/src/Microsoft.AspNet.Mvc.Razor/project.json b/src/Microsoft.AspNet.Mvc.Razor/project.json index f6db9c2334..239715bbc6 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/project.json +++ b/src/Microsoft.AspNet.Mvc.Razor/project.json @@ -8,7 +8,7 @@ "Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" }, "Microsoft.AspNet.Mvc.Core": "6.0.0-*", "Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*", - "Microsoft.CodeAnalysis.CSharp": "1.0.0-beta2-*" + "Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-*" }, "frameworks": { "aspnet50": { diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index d02390f793..6c7b832146 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -9,7 +9,6 @@ using Microsoft.AspNet.Mvc.Internal; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.OptionDescriptors; using Microsoft.AspNet.Mvc.Razor; -using Microsoft.AspNet.Mvc.Razor.Compilation; using Microsoft.AspNet.Mvc.Razor.OptionDescriptors; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Routing; diff --git a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs index fa676ee056..e4f5298c1a 100644 --- a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs +++ b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs @@ -29,14 +29,17 @@ namespace Microsoft.AspNet.Mvc public virtual void BeforeCompile(IBeforeCompileContext context) { - var appEnv = _appServices.GetRequiredService(); + var applicationEnvironment = _appServices.GetRequiredService(); + var compilerOptionsProvider = _appServices.GetRequiredService(); + var compilationSettings = compilerOptionsProvider.GetCompilationSettings(applicationEnvironment); - var setup = new RazorViewEngineOptionsSetup(appEnv); + var setup = new RazorViewEngineOptionsSetup(applicationEnvironment); var sc = new ServiceCollection(); sc.ConfigureOptions(setup); sc.AddMvc(); - var viewCompiler = new RazorPreCompiler(BuildFallbackServiceProvider(sc, _appServices)); + var serviceProvider = BuildFallbackServiceProvider(sc, _appServices); + var viewCompiler = new RazorPreCompiler(serviceProvider, compilationSettings); viewCompiler.CompileViews(context); } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/CompilationOptionsTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/CompilationOptionsTests.cs new file mode 100644 index 0000000000..ed5f3f1a32 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/CompilationOptionsTests.cs @@ -0,0 +1,46 @@ +// 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.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.TestHost; +using RazorWebSite; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + // Test to verify compilation options from the application are used to compile + // precompiled and dynamically compiled views. + public class CompilationOptionsTests + { + private readonly IServiceProvider _provider = TestHelper.CreateServices(nameof(RazorWebSite)); + private readonly Action _app = new Startup().Configure; + + [Fact] + public async Task CompilationOptions_AreUsedByViewsAndPartials() + { + // Arrange +#if ASPNET50 + var expected = +@"This method is running from ASPNET50 + +This method is only defined in ASPNET50"; +#elif ASPNETCORE50 + var expected = +@"This method is running from ASPNETCORE50 + +This method is only defined in ASPNETCORE50"; +#endif + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var body = await client.GetStringAsync("http://localhost/ViewsConsumingCompilationOptions/"); + + // Assert + Assert.Equal(expected, body.Trim()); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs index 2e4945701a..307fb7751e 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/PrecompilationTest.cs @@ -11,6 +11,7 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.Runtime; +using PrecompilationWebSite; using Xunit; namespace Microsoft.AspNet.Mvc.FunctionalTests @@ -18,8 +19,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public class PrecompilationTest { private static readonly TimeSpan _cacheDelayInterval = TimeSpan.FromSeconds(2); - private readonly IServiceProvider _services = TestHelper.CreateServices("PrecompilationWebSite"); - private readonly Action _app = new PrecompilationWebSite.Startup().Configure; + private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(PrecompilationWebSite)); + private readonly Action _app = new Startup().Configure; [Fact] public async Task PrecompiledView_RendersCorrectly() @@ -37,7 +38,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // We will render a view that writes the fully qualified name of the Assembly containing the type of // the view. If the view is precompiled, this assembly will be PrecompilationWebsite. - var assemblyName = typeof(PrecompilationWebSite.Startup).GetTypeInfo().Assembly.GetName().ToString(); + var assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString(); try { @@ -142,6 +143,30 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests } } + [Fact] + public async Task PrecompiledView_UsesCompilationOptionsFromApplication() + { + // Arrange + var assemblyName = typeof(Startup).GetTypeInfo().Assembly.GetName().ToString(); +#if ASPNET50 + var expected = +@"Value set inside ASPNET50 " + assemblyName; +#elif ASPNETCORE50 + var expected = +@"Value set inside ASPNETCORE50 " + assemblyName; +#endif + + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/Home/PrecompiledViewsCanConsumeCompilationOptions"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(expected, responseContent.Trim()); + } + private static Task TouchFile(string viewsDir, string file) { File.AppendAllText(Path.Combine(viewsDir, file), " "); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorCompilationServiceTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs similarity index 100% rename from test/Microsoft.AspNet.Mvc.Razor.Test/RazorCompilationServiceTest.cs rename to test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs new file mode 100644 index 0000000000..ebc6bfb26f --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RoslynCompilationServiceTest.cs @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.Versioning; +using Microsoft.Framework.Runtime; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.Razor +{ + public class RoslynCompilationServiceTest + { + [Fact] + public void Compile_ReturnsUncachedCompilationResultWithCompiledContent() + { + // Arrange + var content = @" +public class MyTestType {}"; + var applicationEnvironment = GetApplicationEnvironment(); + var accessor = GetLoadContextAccessor(); + var libraryManager = GetLibraryManager(); + + var compilerOptionsProvider = new Mock(); + compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath, + applicationEnvironment.RuntimeFramework, + applicationEnvironment.Configuration)) + .Returns(new CompilerOptions()); + var mvcRazorHost = new Mock(); + mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) + .Returns(string.Empty); + + var compilationService = new RoslynCompilationService(applicationEnvironment, + accessor, + libraryManager, + compilerOptionsProvider.Object, + mvcRazorHost.Object); + + // Act + var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content); + + // Assert + var uncachedResult = Assert.IsType(result); + Assert.Equal("MyTestType", result.CompiledType.Name); + Assert.Equal(content, result.CompiledContent); + } + + [Fact] + public void Compile_UsesApplicationsCompilationSettings_ForParsingAndCompilation() + { + // Arrange + var content = @" +#if MY_CUSTOM_DEFINE +public class MyCustomDefinedClass {} +#else +public class MyNonCustomDefinedClass {} +#endif +"; + var applicationEnvironment = GetApplicationEnvironment(); + var accessor = GetLoadContextAccessor(); + var libraryManager = GetLibraryManager(); + + var compilerOptionsProvider = new Mock(); + compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath, + applicationEnvironment.RuntimeFramework, + applicationEnvironment.Configuration)) + .Returns(new CompilerOptions { Defines = new[] { "MY_CUSTOM_DEFINE" } }); + var mvcRazorHost = new Mock(); + mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) + .Returns("My"); + + var compilationService = new RoslynCompilationService(applicationEnvironment, + accessor, + libraryManager, + compilerOptionsProvider.Object, + mvcRazorHost.Object); + + // Act + var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content); + + // Assert + Assert.NotNull(result.CompiledType); + Assert.Equal("MyCustomDefinedClass", result.CompiledType.Name); + } + + [Fact] + public void Compile_ReturnsSingleTypeThatStartsWithMainClassNamePrefix() + { + // Arrange + var content = @" +public class RazorPrefixType {} +public class NotRazorPrefixType {}"; + var applicationEnvironment = GetApplicationEnvironment(); + var accessor = GetLoadContextAccessor(); + var libraryManager = GetLibraryManager(); + + var compilerOptionsProvider = new Mock(); + compilerOptionsProvider.Setup(p => p.GetCompilerOptions(applicationEnvironment.ApplicationBasePath, + applicationEnvironment.RuntimeFramework, + applicationEnvironment.Configuration)) + .Returns(new CompilerOptions()); + var mvcRazorHost = new Mock(); + mvcRazorHost.SetupGet(m => m.MainClassNamePrefix) + .Returns("RazorPrefix"); + + var compilationService = new RoslynCompilationService(applicationEnvironment, + accessor, + libraryManager, + compilerOptionsProvider.Object, + mvcRazorHost.Object); + + // Act + var result = compilationService.Compile(new TestFileInfo { PhysicalPath = "SomePath" }, content); + + // Assert + Assert.NotNull(result.CompiledType); + Assert.Equal("RazorPrefixType", result.CompiledType.Name); + } + + private static ILibraryManager GetLibraryManager() + { + var fileReference = new Mock(); + fileReference.SetupGet(f => f.Path) + .Returns(typeof(string).Assembly.Location); + var libraryExport = new Mock(); + libraryExport.SetupGet(e => e.MetadataReferences) + .Returns(new[] { fileReference.Object }); + libraryExport.SetupGet(e => e.SourceReferences) + .Returns(new ISourceReference[0]); + + var libraryManager = new Mock(); + libraryManager.Setup(l => l.GetAllExports(It.IsAny())) + .Returns(libraryExport.Object); + return libraryManager.Object; + } + + private static IAssemblyLoadContextAccessor GetLoadContextAccessor() + { + var loadContext = new Mock(); + loadContext.Setup(s => s.LoadStream(It.IsAny(), It.IsAny())) + .Returns((Stream stream, Stream pdb) => + { + var memoryStream = (MemoryStream)stream; + return Assembly.Load(memoryStream.ToArray()); + }); + + var accessor = new Mock(); + accessor.Setup(a => a.GetLoadContext(typeof(RoslynCompilationService).Assembly)) + .Returns(loadContext.Object); + return accessor.Object; + } + + private IApplicationEnvironment GetApplicationEnvironment() + { + var applicationEnvironment = new Mock(); + applicationEnvironment.SetupGet(a => a.ApplicationName) + .Returns("MyApp"); + applicationEnvironment.SetupGet(a => a.RuntimeFramework) + .Returns(new FrameworkName("ASPNET", new Version(5, 0))); + applicationEnvironment.SetupGet(a => a.Configuration) + .Returns("Debug"); + applicationEnvironment.SetupGet(a => a.ApplicationBasePath) + .Returns("MyBasePath"); + + return applicationEnvironment.Object; + } + } +} \ No newline at end of file diff --git a/test/WebSites/PrecompilationWebSite/Controllers/HomeController.cs b/test/WebSites/PrecompilationWebSite/Controllers/HomeController.cs index bdff9e4876..7fca420d2e 100644 --- a/test/WebSites/PrecompilationWebSite/Controllers/HomeController.cs +++ b/test/WebSites/PrecompilationWebSite/Controllers/HomeController.cs @@ -11,5 +11,10 @@ namespace PrecompilationWebSite.Controllers { return View(); } + + public IActionResult PrecompiledViewsCanConsumeCompilationOptions() + { + return View("~/Views/ViewsConsumingCompilationOptions/Index"); + } } } diff --git a/test/WebSites/PrecompilationWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml b/test/WebSites/PrecompilationWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml new file mode 100644 index 0000000000..58b2a8d961 --- /dev/null +++ b/test/WebSites/PrecompilationWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml @@ -0,0 +1,9 @@ +@{ +string message = +#if ASPNET50 + "Value set inside ASPNET50 " + GetType().Assembly.FullName; +#elif ASPNETCORE50 + "Value set inside ASPNETCORE50 " + System.Reflection.IntrospectionExtensions.GetTypeInfo(GetType()).Assembly.FullName; +#endif +} +@message diff --git a/test/WebSites/PrecompilationWebSite/project.json b/test/WebSites/PrecompilationWebSite/project.json index 01696e38f0..3f6e124d78 100644 --- a/test/WebSites/PrecompilationWebSite/project.json +++ b/test/WebSites/PrecompilationWebSite/project.json @@ -12,8 +12,16 @@ "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { - "aspnet50": { }, - "aspnetcore50": { } + "aspnet50": { + "compilationOptions": { + "define": [ "CUSTOM_ASPNET50_DEFINE" ] + } + }, + "aspnetcore50": { + "compilationOptions": { + "define": [ "CUSTOM_ASPNETCORE50_DEFINE" ] + } + } }, "webroot": "wwwroot" } diff --git a/test/WebSites/RazorWebSite/Controllers/ViewsConsumingCompilationOptionsController.cs b/test/WebSites/RazorWebSite/Controllers/ViewsConsumingCompilationOptionsController.cs new file mode 100644 index 0000000000..9144cae9a9 --- /dev/null +++ b/test/WebSites/RazorWebSite/Controllers/ViewsConsumingCompilationOptionsController.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.AspNet.Mvc; + +namespace RazorWebSite.Controllers +{ + // Views returned by this controller use #ifdefs for defines specified in project.json + // The intent of this controller is to verify that view compilation uses the app's compilation settings. + public class ViewsConsumingCompilationOptionsController : Controller + { + public ViewResult Index() + { + return View(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Services/FrameworkSpecificHelper.cs b/test/WebSites/RazorWebSite/Services/FrameworkSpecificHelper.cs new file mode 100644 index 0000000000..5c8cbf01f8 --- /dev/null +++ b/test/WebSites/RazorWebSite/Services/FrameworkSpecificHelper.cs @@ -0,0 +1,31 @@ +// 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 RazorWebSite +{ + public class FrameworkSpecificHelper + { + public string ExecuteOperation() + { +#if ASPNET50 + return "This method is running from ASPNET50"; +#elif ASPNETCORE50 + return "This method is running from ASPNETCORE50"; +#endif + } + +#if ASPNET50_CUSTOM_DEFINE + public string ExecuteAspNet50Operation() + { + return "This method is only defined in ASPNET50"; + } +#endif + +#if ASPNETCORE50_CUSTOM_DEFINE + public string ExecuteAspNetCore50Operation() + { + return "This method is only defined in ASPNETCORE50"; + } +#endif + } +} \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Startup.cs b/test/WebSites/RazorWebSite/Startup.cs index ae70ef8d0f..91a7547720 100644 --- a/test/WebSites/RazorWebSite/Startup.cs +++ b/test/WebSites/RazorWebSite/Startup.cs @@ -21,6 +21,7 @@ namespace RazorWebSite services.AddMvc(configuration); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.Configure(options => { var expander = new LanguageViewLocationExpander( diff --git a/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml b/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml new file mode 100644 index 0000000000..71684f7ea6 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml @@ -0,0 +1,3 @@ +@inject FrameworkSpecificHelper MyHelper +@MyHelper.ExecuteOperation() +@Html.Partial("_Partial") \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/_Partial.cshtml b/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/_Partial.cshtml new file mode 100644 index 0000000000..9234f21545 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/_Partial.cshtml @@ -0,0 +1,11 @@ +@inject FrameworkSpecificHelper MyHelper +@{ + string value = +#if ASPNET50_CUSTOM_DEFINE + MyHelper.ExecuteAspNet50Operation(); +#endif +#if ASPNETCORE50_CUSTOM_DEFINE + MyHelper.ExecuteAspNetCore50Operation(); +#endif +} +@value \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/project.json b/test/WebSites/RazorWebSite/project.json index c582a1382f..25816ad6c7 100644 --- a/test/WebSites/RazorWebSite/project.json +++ b/test/WebSites/RazorWebSite/project.json @@ -12,8 +12,16 @@ "Microsoft.AspNet.StaticFiles": "1.0.0-*" }, "frameworks": { - "aspnet50": { }, - "aspnetcore50": { } + "aspnet50": { + "compilationOptions": { + "define": [ "ASPNET50_CUSTOM_DEFINE" ] + } + }, + "aspnetcore50": { + "compilationOptions": { + "define": [ "ASPNETCORE50_CUSTOM_DEFINE" ] + } + } }, "webroot": "wwwroot" } From 487c5464c0b39139bde0b13199bbf2837de3b698 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Sat, 3 Jan 2015 07:31:27 -0800 Subject: [PATCH 030/118] Adding reference to Roslyn to unbreak build --- src/Microsoft.AspNet.Mvc.Razor/project.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Microsoft.AspNet.Mvc.Razor/project.json b/src/Microsoft.AspNet.Mvc.Razor/project.json index 239715bbc6..b2fba52ac5 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/project.json +++ b/src/Microsoft.AspNet.Mvc.Razor/project.json @@ -8,6 +8,7 @@ "Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" }, "Microsoft.AspNet.Mvc.Core": "6.0.0-*", "Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*", + "Microsoft.CodeAnalysis.CSharp": "1.0.0-beta2-*", "Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-*" }, "frameworks": { From 68fcb2bfca31c9fcbd90bca1a3b7d1bdddc1edee Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 5 Jan 2015 10:15:27 -0800 Subject: [PATCH 031/118] Temporarily disabling running UpdateVehicle_WithXml_BindsBodyServicesAndHeaders until we get a resolution for DataContractSerializer behaving differently in CoreCLR. --- test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs index 08db805e1e..332e5b1d8c 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs @@ -1196,6 +1196,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(trackingId, actual.LastUpdatedTrackingId); } +#if ASPNET50 [Fact] public async Task UpdateVehicle_WithXml_BindsBodyServicesAndHeaders() { @@ -1231,6 +1232,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(postedContent.InspectedDates, actual.InspectedDates); Assert.Equal(trackingId, actual.LastUpdatedTrackingId); } +#endif // Simulates a browser based client that does a Ajax post for partial page updates. [Fact] From 9ea535027153094d547c2c001999b16a2598fbac Mon Sep 17 00:00:00 2001 From: Kirthi Krishnamraju Date: Fri, 12 Dec 2014 17:05:41 -0800 Subject: [PATCH 032/118] Added more coverage in functional tests for FlushAsync --- .../FlushPointTest.cs | 66 ++++++++++++++++++- .../RazorWebSite/Controllers/FlushPoint.cs | 10 +++ .../PageWithFlushBeforeLayout.cshtml | 10 +++ .../FlushPoint/PageWithNestedLayout.cshtml | 6 ++ .../Views/FlushPoint/PageWithoutLayout.cshtml | 11 +++- .../_LayoutWithRenderSectionOnly.cshtml | 2 + .../Shared/_NestedLayoutWithFlush.cshtml | 16 +++++ .../Views/Shared/_PartialWithFlush.cshtml | 7 ++ 8 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 test/WebSites/RazorWebSite/Views/FlushPoint/PageWithFlushBeforeLayout.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/FlushPoint/PageWithNestedLayout.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionOnly.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/Shared/_NestedLayoutWithFlush.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/Shared/_PartialWithFlush.cshtml diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/FlushPointTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/FlushPointTest.cs index efcfcba7de..0adaa3fb08 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/FlushPointTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/FlushPointTest.cs @@ -63,7 +63,18 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests waitService.WaitForServer(); // Assert - 3 - Assert.Equal("Final content", GetTrimmedString(stream)); + Assert.Equal("Inside partial", GetTrimmedString(stream)); + waitService.WaitForServer(); + + // Assert - 4 + Assert.Equal(@"After flush inside partial +
", + GetTrimmedString(stream)); + waitService.WaitForServer(); + + // Assert - 5 + Assert.Equal(@"
", + GetTrimmedString(stream)); } [Theory] @@ -105,6 +116,59 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests "More content from layout"), GetTrimmedString(stream)); } + [Fact] + public async Task FlushPointsNestedLayout() + { + // Arrange + var waitService = new WaitService(); + var serviceProvider = GetServiceProvider(waitService); + + var server = TestServer.Create(serviceProvider, _app); + var client = server.CreateClient(); + + // Act + var stream = await client.GetStreamAsync("http://localhost/FlushPoint/PageWithNestedLayout"); + + // Assert - 1 + Assert.Equal(@"Inside Nested Layout + +Nested Page With Layout", + GetTrimmedString(stream)); + waitService.WaitForServer(); + + // Assert - 2 + Assert.Equal("Nested content that takes time to produce", GetTrimmedString(stream)); + } + + [Fact] + public async Task FlushBeforeCallingLayout() + { + var waitService = new WaitService(); + var serviceProvider = GetServiceProvider(waitService); + + var server = TestServer.Create(serviceProvider, _app); + var client = server.CreateClient(); + + var expectedMessage = "A layout page cannot be rendered after 'FlushAsync' has been invoked."; + + // Act + var stream = await client.GetStreamAsync("http://localhost/FlushPoint/PageWithFlushBeforeLayout"); + + // Assert - 1 + Assert.Equal("Initial content", GetTrimmedString(stream)); + waitService.WaitForServer(); + + //Assert - 2 + try + { + GetTrimmedString(stream); + } + catch (Exception ex) + { + Assert.Equal(expectedMessage, ex.InnerException.Message); + } + } + private IServiceProvider GetServiceProvider(WaitService waitService) { var services = new ServiceCollection(); diff --git a/test/WebSites/RazorWebSite/Controllers/FlushPoint.cs b/test/WebSites/RazorWebSite/Controllers/FlushPoint.cs index f8f47ebb23..d8625a98b1 100644 --- a/test/WebSites/RazorWebSite/Controllers/FlushPoint.cs +++ b/test/WebSites/RazorWebSite/Controllers/FlushPoint.cs @@ -28,5 +28,15 @@ namespace RazorWebSite { return View("PageWithSectionInvokedViaRenderSectionAsync"); } + + public ViewResult PageWithNestedLayout() + { + return View(); + } + + public ViewResult PageWithFlushBeforeLayout() + { + return View(); + } } } \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithFlushBeforeLayout.cshtml b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithFlushBeforeLayout.cshtml new file mode 100644 index 0000000000..3473b46cdd --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithFlushBeforeLayout.cshtml @@ -0,0 +1,10 @@ +@inject WaitService WaitService +Initial content +@await FlushAsync() +@{ + WaitService.WaitForClient(); +} + +@{ + Layout = "/Views/Shared/_LayoutWithFlush.cshtml"; +} diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithNestedLayout.cshtml b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithNestedLayout.cshtml new file mode 100644 index 0000000000..9dd03ccac4 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithNestedLayout.cshtml @@ -0,0 +1,6 @@ +@inject WaitService WaitService +@{ + Layout = "/Views/Shared/_NestedLayoutWithFlush.cshtml"; + ViewBag.Title = "Nested Page With Layout"; +} +@ViewBag.Title \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithoutLayout.cshtml b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithoutLayout.cshtml index b25b173026..217ba18738 100644 --- a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithoutLayout.cshtml +++ b/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithoutLayout.cshtml @@ -9,7 +9,16 @@ Secondary content @{ WaitService.WaitForClient(); } -Final content +@{ + await Html.RenderPartialAsync("_PartialWithFlush"); +} +@using (Html.BeginForm()) +{ + @Html.TextBox("Name1") + @await FlushAsync() + WaitService.WaitForClient(); + @Html.TextBox("Name2") +} @{ WaitService.NotifyClient(); } \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionOnly.cshtml b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionOnly.cshtml new file mode 100644 index 0000000000..c3caff06ad --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionOnly.cshtml @@ -0,0 +1,2 @@ +@RenderBody() +@await RenderSectionAsync("nestedcontent") \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/Shared/_NestedLayoutWithFlush.cshtml b/test/WebSites/RazorWebSite/Views/Shared/_NestedLayoutWithFlush.cshtml new file mode 100644 index 0000000000..d127d570ac --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/Shared/_NestedLayoutWithFlush.cshtml @@ -0,0 +1,16 @@ +@inject WaitService WaitService +@{ + Layout = "/Views/Shared/_LayoutWithRenderSectionOnly.cshtml"; + ViewBag.Title = "Page With Layout"; +} +Inside Nested Layout +@RenderBody() +@section nestedcontent +{ + @{ + await FlushAsync(); + WaitService.WaitForClient(); + } + Nested content that takes time to produce +} + diff --git a/test/WebSites/RazorWebSite/Views/Shared/_PartialWithFlush.cshtml b/test/WebSites/RazorWebSite/Views/Shared/_PartialWithFlush.cshtml new file mode 100644 index 0000000000..de296ced2a --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/Shared/_PartialWithFlush.cshtml @@ -0,0 +1,7 @@ +@inject WaitService WaitService +Inside partial +@await FlushAsync() +@{ + WaitService.WaitForClient(); +} +After flush inside partial \ No newline at end of file From eb7283fced7935cde22781025a1c532f60b13e20 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Thu, 18 Dec 2014 15:31:16 -0800 Subject: [PATCH 033/118] added BadRequest and Created Action Results with related unit and functional tests. --- Mvc.sln | 17 +- .../ActionResults/BadRequestResult.cs | 20 ++ .../ActionResults/CreatedAtActionResult.cs | 71 +++++++ .../ActionResults/CreatedAtRouteResult.cs | 74 +++++++ .../ActionResults/CreatedResult.cs | 37 ++++ .../ActionResults/ObjectResult.cs | 18 ++ src/Microsoft.AspNet.Mvc.Core/Controller.cs | 122 ++++++++++++ .../ActionResults/BadRequestResultTests.cs | 20 ++ .../CreatedAtActionResultTests.cs | 126 ++++++++++++ .../CreatedAtRouteResultTests.cs | 140 +++++++++++++ .../ActionResults/CreatedResultTests.cs | 98 ++++++++++ .../ControllerTests.cs | 179 +++++++++++++++++ .../ActionResultTests.cs | 185 ++++++++++++++++++ .../project.json | 3 +- .../ActionResultsWebSite.kproj | 20 ++ .../ActionResultsVerificationController.cs | 78 ++++++++ .../ActionResultsWebSite/Models/DummyClass.cs | 14 ++ test/WebSites/ActionResultsWebSite/Startup.cs | 34 ++++ .../ActionResultsWebSite/project.json | 19 ++ 19 files changed, 1273 insertions(+), 2 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/ActionResults/BadRequestResult.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedResult.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/BadRequestResultTests.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedAtActionResultTests.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedAtRouteResultTests.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedResultTests.cs create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs create mode 100644 test/WebSites/ActionResultsWebSite/ActionResultsWebSite.kproj create mode 100644 test/WebSites/ActionResultsWebSite/Controllers/ActionResultsVerificationController.cs create mode 100644 test/WebSites/ActionResultsWebSite/Models/DummyClass.cs create mode 100644 test/WebSites/ActionResultsWebSite/Startup.cs create mode 100644 test/WebSites/ActionResultsWebSite/project.json diff --git a/Mvc.sln b/Mvc.sln index f1bc715fb6..a47da8f452 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22410.0 +VisualStudioVersion = 14.0.22416.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" EndProject @@ -112,6 +112,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CompositeViewEngineWebSite" 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}") = "ActionResultsWebSite", "test\WebSites\ActionResultsWebSite\ActionResultsWebSite.kproj", "{0A6BB4C0-48D3-4E7F-952B-B8917345E075}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -608,6 +610,18 @@ Global {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 + {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -662,5 +676,6 @@ Global {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} + {0A6BB4C0-48D3-4E7F-952B-B8917345E075} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/BadRequestResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/BadRequestResult.cs new file mode 100644 index 0000000000..052d026a67 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/BadRequestResult.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. + +namespace Microsoft.AspNet.Mvc +{ + /// + /// A that when + /// executed will produce a Bad Request (400) response. + /// + public class BadRequestResult : HttpStatusCodeResult + { + /// + /// Creates a new instance. + /// + public BadRequestResult() + : base(400) + { + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs new file mode 100644 index 0000000000..764b9bbc62 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs @@ -0,0 +1,71 @@ +// 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 +{ + /// + /// An that returns a Created (201) response with a Location header. + /// + public class CreatedAtActionResult : ObjectResult + { + /// + /// Initializes a new instance of the with the values + /// provided. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to format in the entity body. + public CreatedAtActionResult(string actionName, + string controllerName, + object routeValues, + object value) + : base(value) + { + ActionName = actionName; + ControllerName = controllerName; + RouteValues = TypeHelper.ObjectToDictionary(routeValues); + StatusCode = 201; + } + + /// + /// Gets or sets the used to generate URLs. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + /// Gets the name of the action to use for generating the URL. + /// + public string ActionName { get; private set; } + + /// + /// Gets the name of the controller to use for generating the URL. + /// + public string ControllerName { get; private set; } + + /// + /// Gets the route data to use for generating the URL. + /// + public IDictionary RouteValues { get; private set; } + + /// + protected override void OnFormatting([NotNull] ActionContext context) + { + var urlHelper = UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService(); + + var url = urlHelper.Action(ActionName, ControllerName, RouteValues); + + if (string.IsNullOrEmpty(url)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + context.HttpContext.Response.Headers.Add("Location", new string[] { url }); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs new file mode 100644 index 0000000000..d1e99b447d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs @@ -0,0 +1,74 @@ +// 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 +{ + /// + /// An that returns a Created (201) response with a Location header. + /// + public class CreatedAtRouteResult : ObjectResult + { + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The route data to use for generating the URL. + /// The value to format in the entity body. + public CreatedAtRouteResult(object routeValues, object value) + : this(routeName: null, routeValues: routeValues, value: value) + { + } + + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The value to format in the entity body. + public CreatedAtRouteResult(string routeName, + object routeValues, + object value) + : base(value) + { + RouteName = routeName; + RouteValues = TypeHelper.ObjectToDictionary(routeValues); + StatusCode = 201; + } + + /// + /// Gets or sets the used to generate URLs. + /// + public IUrlHelper UrlHelper { get; set; } + + /// + /// Gets the name of the route to use for generating the URL. + /// + public string RouteName { get; private set; } + + /// + /// Gets the route data to use for generating the URL. + /// + public IDictionary RouteValues { get; private set; } + + /// + protected override void OnFormatting([NotNull] ActionContext context) + { + var urlHelper = UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService(); + + var url = urlHelper.RouteUrl(RouteName, RouteValues); + + if (string.IsNullOrEmpty(url)) + { + throw new InvalidOperationException(Resources.NoRoutesMatched); + } + + context.HttpContext.Response.Headers.Add("Location", new string[] { url }); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedResult.cs new file mode 100644 index 0000000000..42f14a53d5 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedResult.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 +{ + /// + /// An that returns a Created (201) response with a Location header. + /// + public class CreatedResult : ObjectResult + { + /// + /// Initializes a new instance of the class with the values + /// provided. + /// + /// The location at which the content has been created. + /// The value to format in the entity body. + public CreatedResult([NotNull] string location, object value) + : base(value) + { + Location = location; + StatusCode = 201; + } + + /// + /// Gets the location at which the content has been created. + /// + public string Location { get; private set; } + + /// + protected override void OnFormatting([NotNull] ActionContext context) + { + context.HttpContext.Response.Headers.Add("Location", new string[] { Location }); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs index ce3b91e1d5..e8aec35b4a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs @@ -20,6 +20,11 @@ namespace Microsoft.AspNet.Mvc public Type DeclaredType { get; set; } + /// + /// Gets or sets the HTTP status code. + /// + public int? StatusCode { get; set; } + public ObjectResult(object value) { Value = value; @@ -45,6 +50,12 @@ namespace Microsoft.AspNet.Mvc return; } + if (StatusCode != null) + { + context.HttpContext.Response.StatusCode = (int)StatusCode; + } + + OnFormatting(context); await selectedFormatter.WriteAsync(formatterContext); } @@ -216,5 +227,12 @@ namespace Microsoft.AspNet.Mvc return formatters; } + + /// + /// This method is called before the formatter writes to the output stream. + /// + protected virtual void OnFormatting([NotNull] ActionContext context) + { + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index 2dc1125d70..ea16319d0b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -617,6 +617,128 @@ namespace Microsoft.AspNet.Mvc return new HttpNotFoundResult(); } + /// + /// Creates an that produces a Bad Request (400) response. + /// + /// The created for the response. + [NonAction] + public virtual BadRequestResult HttpBadRequest() + { + return new BadRequestResult(); + } + + /// + /// Creates a object that produces a Created (201) response. + /// + /// The URI at which the content has been created. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedResult Created([NotNull] string uri, object value) + { + return new CreatedResult(uri, value); + } + + /// + /// Creates a object that produces a Created (201) response. + /// + /// The URI at which the content has been created. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedResult Created([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); + } + + /// + /// Creates a object that produces a Created (201) response. + /// + /// The name of the action to use for generating the URL. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedAtActionResult CreatedAtAction(string actionName, object value) + { + return CreatedAtAction(actionName, routeValues: null, value: value); + } + + /// + /// Creates a object that produces a Created (201) response. + /// + /// The name of the action to use for generating the URL. + /// The route data to use for generating the URL. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedAtActionResult CreatedAtAction(string actionName, object routeValues, object value) + { + return CreatedAtAction(actionName, controllerName: null, routeValues: routeValues, value: value); + } + + /// + /// Creates a object that produces a Created (201) response. + /// + /// The name of the action to use for generating the URL. + /// The name of the controller to use for generating the URL. + /// The route data to use for generating the URL. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedAtActionResult CreatedAtAction(string actionName, + string controllerName, + object routeValues, + object value) + { + return new CreatedAtActionResult(actionName, controllerName, routeValues, value); + } + + /// + /// Creates a object that produces a Created (201) response. + /// + /// The name of the route to use for generating the URL. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object value) + { + return CreatedAtRoute(routeName, routeValues: null, value: value); + } + + /// + /// Creates a object that produces a Created (201) response. + /// + /// The route data to use for generating the URL. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedAtRouteResult CreatedAtRoute(object routeValues, object value) + { + return CreatedAtRoute(routeName: null, routeValues: routeValues, value: value); + } + + /// + /// Creates a object that produces a Created (201) response. + /// + /// The name of the route to use for generating the URL. + /// The route data to use for generating the URL. + /// The content value to format in the entity body. + /// The created for the response. + [NonAction] + public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object routeValues, object value) + { + return new CreatedAtRouteResult(routeName, routeValues, value); + } + /// /// Called before the action method is invoked. /// diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/BadRequestResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/BadRequestResultTests.cs new file mode 100644 index 0000000000..fbb5cbb18c --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/BadRequestResultTests.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 Xunit; + +namespace Microsoft.AspNet.Mvc +{ + public class BadRequestResultTests + { + [Fact] + public void BadRequestResult_InitializesStatusCode() + { + // Arrange & act + var badRequest = new BadRequestResult(); + + // Assert + Assert.Equal(400, badRequest.StatusCode); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedAtActionResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedAtActionResultTests.cs new file mode 100644 index 0000000000..fab57bcedd --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedAtActionResultTests.cs @@ -0,0 +1,126 @@ +// 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.IO; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.PipelineCore.Collections; +using Microsoft.AspNet.Routing; +using Microsoft.AspNet.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc +{ + public class CreatedAtActionResultTests + { + [Fact] + public async Task CreatedAtActionResult_ReturnsStatusCode_SetsLocationHeader() + { + // Arrange + var expectedUrl = "testAction"; + var response = GetMockedHttpResponseObject(); + var httpContext = GetHttpContext(response); + var actionContext = GetActionContext(httpContext); + var urlHelper = GetMockUrlHelper(expectedUrl); + + // Act + var result = new CreatedAtActionResult( + actionName: expectedUrl, + controllerName: null, + routeValues: null, + value: null); + + result.UrlHelper = urlHelper; + await result.ExecuteResultAsync(actionContext); + + // Assert + Assert.Equal(201, response.StatusCode); + Assert.Equal(expectedUrl, response.Headers["Location"]); + } + + [Fact] + public async Task CreatedAtActionResult_ThrowsOnNullUrl() + { + // Arrange + var response = GetMockedHttpResponseObject(); + var httpContext = GetHttpContext(response); + var actionContext = GetActionContext(httpContext); + var urlHelper = GetMockUrlHelper(returnValue: null); + + var result = new CreatedAtActionResult( + actionName: null, + controllerName: null, + routeValues: null, + value: null); + + result.UrlHelper = urlHelper; + + // Act & Assert + await ExceptionAssert.ThrowsAsync( + async () => await result.ExecuteResultAsync(actionContext), + "No route matches the supplied values."); + } + + private static HttpResponse GetMockedHttpResponseObject() + { + var stream = new MemoryStream(); + var httpResponse = new Mock(); + httpResponse.SetupProperty(o => o.StatusCode); + httpResponse.Setup(o => o.Headers).Returns( + new HeaderDictionary(new Dictionary())); + httpResponse.SetupGet(o => o.Body).Returns(stream); + return httpResponse.Object; + } + + private static ActionContext GetActionContext(HttpContext httpContext) + { + var routeData = new RouteData(); + routeData.Routers.Add(Mock.Of()); + + return new ActionContext(httpContext, + routeData, + new ActionDescriptor()); + } + + private static HttpContext GetHttpContext(HttpResponse response) + { + var httpContext = new Mock(); + + httpContext.Setup(o => o.Response) + .Returns(response); + httpContext.Setup(o => o.RequestServices.GetService(typeof(IOutputFormattersProvider))) + .Returns(new TestOutputFormatterProvider()); + httpContext.Setup(o => o.Request.PathBase) + .Returns(new PathString("")); + + return httpContext.Object; + } + + private static IUrlHelper GetMockUrlHelper(string returnValue) + { + var urlHelper = new Mock(); + urlHelper.Setup(o => o.Action(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny(), It.IsAny())).Returns(returnValue); + + return urlHelper.Object; + } + + private class TestOutputFormatterProvider : IOutputFormattersProvider + { + public IReadOnlyList OutputFormatters + { + get + { + return new List() + { + new TextPlainFormatter(), + new JsonOutputFormatter() + }; + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedAtRouteResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedAtRouteResultTests.cs new file mode 100644 index 0000000000..d7bfe56946 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedAtRouteResultTests.cs @@ -0,0 +1,140 @@ +// 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.IO; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.PipelineCore.Collections; +using Microsoft.AspNet.Routing; +using Microsoft.AspNet.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc +{ + public class CreatedAtRouteResultTests + { + public static IEnumerable CreatedAtRouteData + { + get + { + yield return new object[] { null }; + yield return + new object[] { + new Dictionary() { { "hello", "world" } } + }; + yield return + new object[] { + new RouteValueDictionary(new Dictionary() { + { "test", "case" }, + { "sample", "route" } + }) + }; + } + } + + [Theory] + [MemberData(nameof(CreatedAtRouteData))] + public async Task CreatedAtRouteResult_ReturnsStatusCode_SetsLocationHeader(object values) + { + // Arrange + var expectedUrl = "testAction"; + var response = GetMockedHttpResponseObject(); + var httpContext = GetHttpContext(response); + var actionContext = GetActionContext(httpContext); + var urlHelper = GetMockUrlHelper(expectedUrl); + + // Act + var result = new CreatedAtRouteResult(routeName: null, routeValues: values, value: null); + result.UrlHelper = urlHelper; + await result.ExecuteResultAsync(actionContext); + + // Assert + Assert.Equal(201, response.StatusCode); + Assert.Equal(expectedUrl, response.Headers["Location"]); + } + + [Fact] + public async Task CreatedAtRouteResult_ThrowsOnNullUrl() + { + // Arrange + var response = GetMockedHttpResponseObject(); + var httpContext = GetHttpContext(response); + var actionContext = GetActionContext(httpContext); + var urlHelper = GetMockUrlHelper(returnValue: null); + + var result = new CreatedAtRouteResult( + routeName: null, + routeValues: new Dictionary(), + value: null); + + result.UrlHelper = urlHelper; + + // Act & Assert + await ExceptionAssert.ThrowsAsync( + async () => await result.ExecuteResultAsync(actionContext), + "No route matches the supplied values."); + } + + private static HttpResponse GetMockedHttpResponseObject() + { + var stream = new MemoryStream(); + var httpResponse = new Mock(); + httpResponse.SetupProperty(o => o.StatusCode); + httpResponse.Setup(o => o.Headers).Returns( + new HeaderDictionary(new Dictionary())); + httpResponse.SetupGet(o => o.Body).Returns(stream); + return httpResponse.Object; + } + + private static ActionContext GetActionContext(HttpContext httpContext) + { + var routeData = new RouteData(); + routeData.Routers.Add(Mock.Of()); + + return new ActionContext(httpContext, + routeData, + new ActionDescriptor()); + } + + private static HttpContext GetHttpContext(HttpResponse response) + { + var httpContext = new Mock(); + + httpContext.Setup(o => o.Response) + .Returns(response); + httpContext.Setup(o => o.RequestServices.GetService(typeof(IOutputFormattersProvider))) + .Returns(new TestOutputFormatterProvider()); + httpContext.Setup(o => o.Request.PathBase) + .Returns(new PathString("")); + + return httpContext.Object; + } + + private static IUrlHelper GetMockUrlHelper(string returnValue) + { + var urlHelper = new Mock(); + urlHelper.Setup(o => o.RouteUrl(It.IsAny(), It.IsAny(), It.IsAny(), + It.IsAny(), It.IsAny())).Returns(returnValue); + + return urlHelper.Object; + } + + private class TestOutputFormatterProvider : IOutputFormattersProvider + { + public IReadOnlyList OutputFormatters + { + get + { + return new List() + { + new TextPlainFormatter(), + new JsonOutputFormatter() + }; + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedResultTests.cs new file mode 100644 index 0000000000..f1f3fc7a44 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/CreatedResultTests.cs @@ -0,0 +1,98 @@ +// 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.IO; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.PipelineCore.Collections; +using Microsoft.AspNet.Routing; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc +{ + public class CreatedResultTests + { + [Fact] + public void CreatedResult_SetsLocation() + { + // Arrange + var location = "http://test/location"; + + // Act + var result = new CreatedResult(location, "testInput"); + + // Assert + Assert.Same(location, result.Location); + } + + [Fact] + public async Task CreatedResult_ReturnsStatusCode_SetsLocationHeader() + { + // Arrange + var location = "/test/"; + var response = GetMockedHttpResponseObject(); + var httpContext = GetHttpContext(response); + var actionContext = GetActionContext(httpContext); + var result = new CreatedResult(location, "testInput"); + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + Assert.Equal(201, response.StatusCode); + Assert.Equal(location, response.Headers["Location"]); + } + + private static HttpResponse GetMockedHttpResponseObject() + { + var stream = new MemoryStream(); + var httpResponse = new Mock(); + httpResponse.SetupProperty(o => o.StatusCode); + httpResponse.Setup(o => o.Headers).Returns( + new HeaderDictionary(new Dictionary())); + httpResponse.SetupGet(o => o.Body).Returns(stream); + return httpResponse.Object; + } + + private static ActionContext GetActionContext(HttpContext httpContext) + { + var routeData = new RouteData(); + routeData.Routers.Add(Mock.Of()); + + return new ActionContext(httpContext, + routeData, + new ActionDescriptor()); + } + + private static HttpContext GetHttpContext(HttpResponse response) + { + var httpContext = new Mock(); + + httpContext.Setup(o => o.Response) + .Returns(response); + httpContext.Setup(o => o.RequestServices.GetService(typeof(IOutputFormattersProvider))) + .Returns(new TestOutputFormatterProvider()); + httpContext.Setup(o => o.Request.PathBase) + .Returns(new PathString("")); + + return httpContext.Object; + } + + private class TestOutputFormatterProvider : IOutputFormattersProvider + { + public IReadOnlyList OutputFormatters + { + get + { + return new List() + { + new TextPlainFormatter(), + new JsonOutputFormatter() + }; + } + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs index e097c02950..55435cfc16 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs @@ -373,6 +373,171 @@ namespace Microsoft.AspNet.Mvc.Test Assert.Equal(expected, resultPermanent.RouteValues); } + [Fact] + public void Created_WithStringParameter_SetsCreatedLocation() + { + // Arrange + var controller = new Controller(); + var uri = "http://test/url"; + + // Act + var result = controller.Created(uri, null); + + // Assert + Assert.IsType(result); + Assert.Equal(201, result.StatusCode); + Assert.Same(uri, result.Location); + } + + [Fact] + public void Created_WithAbsoluteUriParameter_SetsCreatedLocation() + { + // Arrange + var controller = new Controller(); + var uri = new Uri("http://test/url"); + + // Act + var result = controller.Created(uri, null); + + // Assert + Assert.IsType(result); + Assert.Equal(201, result.StatusCode); + Assert.Equal(uri.OriginalString, result.Location); + } + + [Fact] + public void Created_WithRelativeUriParameter_SetsCreatedLocation() + { + // Arrange + var controller = new Controller(); + var uri = new Uri("/test/url", UriKind.Relative); + + // Act + var result = controller.Created(uri, null); + + // Assert + Assert.IsType(result); + Assert.Equal(201, result.StatusCode); + Assert.Equal(uri.OriginalString, result.Location); + } + + [Fact] + public void CreatedAtAction_WithParameterActionName_SetsResultActionName() + { + // Arrange + var controller = new Controller(); + + // Act + var result = controller.CreatedAtAction("SampleAction", null); + + // Assert + Assert.IsType(result); + Assert.Equal(201, result.StatusCode); + Assert.Equal("SampleAction", result.ActionName); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + [InlineData("SampleController")] + public void CreatedAtAction_WithActionControllerAndNullRouteValue_SetsSameValue( + string controllerName) + { + // Arrange + var controller = new Controller(); + + // Act + var result = controller.CreatedAtAction("SampleAction", controllerName, null, null); + + // Assert + Assert.IsType(result); + Assert.Equal(201, result.StatusCode); + Assert.Equal("SampleAction", result.ActionName); + Assert.Equal(controllerName, result.ControllerName); + } + + [Fact] + public void CreatedAtAction_WithActionControllerRouteValues_SetsSameValues() + { + // Arrange + var controller = new Controller(); + var expected = new Dictionary + { + { "test", "case" }, + { "sample", "route" }, + }; + + // Act + var result = controller.CreatedAtAction( + "SampleAction", + "SampleController", + new RouteValueDictionary(expected), null); + + // Assert + Assert.IsType(result); + Assert.Equal(201, result.StatusCode); + Assert.Equal("SampleAction", result.ActionName); + Assert.Equal("SampleController", result.ControllerName); + Assert.Equal(expected, result.RouteValues); + } + + [Fact] + public void CreatedAtRoute_WithParameterRouteName_SetsResultSameRouteName() + { + // Arrange + var controller = new Controller(); + var routeName = "SampleRoute"; + + // Act + var result = controller.CreatedAtRoute(routeName, null); + + // Assert + Assert.IsType(result); + Assert.Same(routeName, result.RouteName); + } + + [Fact] + public void CreatedAtRoute_WithParameterRouteValues_SetsResultSameRouteValues() + { + // Arrange + var controller = new Controller(); + var expected = new Dictionary + { + { "test", "case" }, + { "sample", "route" }, + }; + + // Act + var result = controller.CreatedAtRoute(new RouteValueDictionary(expected), null); + + // Assert + Assert.IsType(result); + Assert.Equal(201, result.StatusCode); + Assert.Equal(expected, result.RouteValues); + } + + [Fact] + public void CreatedAtRoute_WithParameterRouteNameAndValues_SetsResultSameProperties() + { + // Arrange + var controller = new Controller(); + var routeName = "SampleRoute"; + var expected = new Dictionary + { + { "test", "case" }, + { "sample", "route" }, + }; + + // Act + var result = controller.CreatedAtRoute(routeName, new RouteValueDictionary(expected), null); + + // Assert + Assert.IsType(result); + Assert.Equal(201, result.StatusCode); + Assert.Same(routeName, result.RouteName); + Assert.Equal(expected, result.RouteValues); + } + [Fact] public void File_WithContents() { @@ -490,6 +655,20 @@ namespace Microsoft.AspNet.Mvc.Test Assert.Equal(404, result.StatusCode); } + [Fact] + public void BadRequest_SetsStatusCode() + { + // Arrange + var controller = new Controller(); + + // Act + var result = controller.HttpBadRequest(); + + // Assert + Assert.IsType(result); + Assert.Equal(400, result.StatusCode); + } + [Theory] [MemberData(nameof(PublicNormalMethodsFromController))] public void NonActionAttribute_IsOnEveryPublicNormalMethodFromController(MethodInfo method) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs new file mode 100644 index 0000000000..68e4bc31b1 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs @@ -0,0 +1,185 @@ +// 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.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using ActionResultsWebSite; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.TestHost; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class ActionResultTests + { + private readonly IServiceProvider _provider = TestHelper.CreateServices("ActionResultsWebSite"); + private readonly Action _app = new Startup().Configure; + + [Fact] + public async Task BadRequestResult_CanBeReturned() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + var input = "{\"SampleInt\":10}"; + + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ActionResultsVerification/GetBadResult"); + + request.Content = new StringContent(input, Encoding.UTF8, "application/json"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.Equal("", await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task CreatedResult_SetsRelativePathInLocationHeader() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ActionResultsVerification/GetCreatedRelative"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.Equal("1", response.Headers.Location.OriginalString); + Assert.Equal("{\"SampleInt\":10,\"SampleString\":\"Foo\"}", await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task CreatedResult_SetsAbsolutePathInLocationHeader() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ActionResultsVerification/GetCreatedAbsolute"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.Equal("/ActionResultsVerification/GetDummy/1", response.Headers.Location.OriginalString); + Assert.Equal("{\"SampleInt\":10,\"SampleString\":\"Foo\"}", await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task CreatedResult_SetsQualifiedPathInLocationHeader() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ActionResultsVerification/GetCreatedQualified"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.Equal( + "http://localhost/ActionResultsVerification/GetDummy/1", + response.Headers.Location.OriginalString); + Assert.Equal("{\"SampleInt\":10,\"SampleString\":\"Foo\"}", await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task CreatedResult_SetsUriInLocationHeader() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ActionResultsVerification/GetCreatedUri"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.Equal("/ActionResultsVerification/GetDummy/1", response.Headers.Location.OriginalString); + Assert.Equal("{\"SampleInt\":10,\"SampleString\":\"Foo\"}", await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task CreatedAtActionResult_GeneratesUri_WithActionAndController() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ActionResultsVerification/GetCreatedAtAction"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.Equal("/ActionResultsVerification/GetDummy/1", response.Headers.Location.OriginalString); + Assert.Equal("{\"SampleInt\":10,\"SampleString\":\"Foo\"}", await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task CreatedAtRouteResult_GeneratesUri_WithRouteValues() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ActionResultsVerification/GetCreatedAtRoute"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.Equal("/ActionResultsVerification/GetDummy/1", response.Headers.Location.OriginalString); + Assert.Equal("{\"SampleInt\":10,\"SampleString\":\"Foo\"}", await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task CreatedAtRouteResult_GeneratesUri_WithRouteName() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ActionResultsVerification/GetCreatedAtRouteWithRouteName"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + Assert.Equal("/foo/ActionResultsVerification/GetDummy/1", response.Headers.Location.OriginalString); + Assert.Equal("{\"SampleInt\":10,\"SampleString\":\"Foo\"}", await response.Content.ReadAsStringAsync()); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json index 5f50ff8f3b..13f7590d1c 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json @@ -3,6 +3,7 @@ "warningsAsErrors": "true" }, "dependencies": { + "ActionResultsWebSite": "1.0.0", "ActivatorWebSite": "1.0.0", "AddServicesWebSite": "1.0.0", "AntiForgeryWebSite": "1.0.0", @@ -21,7 +22,7 @@ "RoutingWebSite": "1.0.0", "RazorWebSite": "1.0.0", "RazorInstrumentationWebsite": "1.0.0", - "RazorViewEngineOptionsWebsite": "1.0.0", + "RazorViewEngineOptionsWebsite": "1.0.0", "RequestServicesWebSite": "1.0.0", "TagHelperSample.Web": "1.0.0", "TagHelpersWebSite": "1.0.0", diff --git a/test/WebSites/ActionResultsWebSite/ActionResultsWebSite.kproj b/test/WebSites/ActionResultsWebSite/ActionResultsWebSite.kproj new file mode 100644 index 0000000000..38f9273e1c --- /dev/null +++ b/test/WebSites/ActionResultsWebSite/ActionResultsWebSite.kproj @@ -0,0 +1,20 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 0a6bb4c0-48d3-4e7f-952b-b8917345e075 + + + + + + + 2.0 + 41642 + + + \ No newline at end of file diff --git a/test/WebSites/ActionResultsWebSite/Controllers/ActionResultsVerificationController.cs b/test/WebSites/ActionResultsWebSite/Controllers/ActionResultsVerificationController.cs new file mode 100644 index 0000000000..bffd5cd400 --- /dev/null +++ b/test/WebSites/ActionResultsWebSite/Controllers/ActionResultsVerificationController.cs @@ -0,0 +1,78 @@ +// 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.AspNet.Mvc; + +namespace ActionResultsWebSite +{ + public class ActionResultsVerificationController : Controller + { + public IActionResult Index([FromBody]DummyClass test) + { + if (!ModelState.IsValid) + { + return new BadRequestResult(); + } + + return Content("Hello World!"); + } + + public IActionResult GetBadResult() + { + return new BadRequestResult(); + } + + public IActionResult GetCreatedRelative() + { + return Created("1", CreateDummy()); + } + + public IActionResult GetCreatedAbsolute() + { + return Created("/ActionResultsVerification/GetDummy/1", CreateDummy()); + } + + public IActionResult GetCreatedQualified() + { + return Created("http://localhost/ActionResultsVerification/GetDummy/1", CreateDummy()); + } + + public IActionResult GetCreatedUri() + { + return Created(new Uri("/ActionResultsVerification/GetDummy/1", UriKind.Relative), CreateDummy()); + } + + public IActionResult GetCreatedAtAction() + { + var values = new { id = 1 }; + return CreatedAtAction("GetDummy", "ActionResultsVerification", values, CreateDummy()); + } + + public IActionResult GetCreatedAtRoute() + { + var values = new { controller = "ActionResultsVerification", Action = "GetDummy", id = 1 }; + return CreatedAtRoute(null, values, CreateDummy()); + } + + public IActionResult GetCreatedAtRouteWithRouteName() + { + var values = new { controller = "ActionResultsVerification", Action = "GetDummy", id = 1 }; + return CreatedAtRoute("custom-route", values, CreateDummy()); + } + + public DummyClass GetDummy(int id) + { + return CreateDummy(); + } + + private DummyClass CreateDummy() + { + return new DummyClass() + { + SampleInt = 10, + SampleString = "Foo" + }; + } + } +} \ No newline at end of file diff --git a/test/WebSites/ActionResultsWebSite/Models/DummyClass.cs b/test/WebSites/ActionResultsWebSite/Models/DummyClass.cs new file mode 100644 index 0000000000..f9a08ad3dd --- /dev/null +++ b/test/WebSites/ActionResultsWebSite/Models/DummyClass.cs @@ -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. + +using System.ComponentModel.DataAnnotations; + +namespace ActionResultsWebSite +{ + public class DummyClass + { + public int SampleInt { get; set; } + + public string SampleString { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/ActionResultsWebSite/Startup.cs b/test/WebSites/ActionResultsWebSite/Startup.cs new file mode 100644 index 0000000000..3b7e8f4020 --- /dev/null +++ b/test/WebSites/ActionResultsWebSite/Startup.cs @@ -0,0 +1,34 @@ +// 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.Builder; +using Microsoft.AspNet.Routing; +using Microsoft.Framework.DependencyInjection; + +namespace ActionResultsWebSite +{ + public class Startup + { + public void Configure(IApplicationBuilder app) + { + var configuration = app.GetTestConfiguration(); + + app.UseServices(services => + { + services.AddMvc(configuration); + }); + + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller}/{action}/{id?}", + defaults: new { controller = "ActionResultsVerification", action = "Index" }); + + routes.MapRoute( + name: "custom-route", + template: "foo/{controller}/{action}/{id?}"); + }); + } + } +} \ No newline at end of file diff --git a/test/WebSites/ActionResultsWebSite/project.json b/test/WebSites/ActionResultsWebSite/project.json new file mode 100644 index 0000000000..464f3bbc61 --- /dev/null +++ b/test/WebSites/ActionResultsWebSite/project.json @@ -0,0 +1,19 @@ +{ + "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" + }, + "dependencies": { + "Kestrel": "1.0.0-*", + "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.StaticFiles": "1.0.0-*" + }, + "frameworks": { + "aspnet50": { }, + "aspnetcore50": { } + }, + "webroot": "wwwroot" +} \ No newline at end of file From f649fb45c86cf4988f9520026ad08bb6afb722e9 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 5 Jan 2015 13:41:35 -0800 Subject: [PATCH 034/118] Adding TestConfiguration to NoFun.sln to avoid it from modifying the sln on open --- Mvc.NoFun.sln | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index 0f306eb7d7..4ec175715e 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22013.1 +VisualStudioVersion = 14.0.22416.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" EndProject @@ -46,6 +46,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.WebApi EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.WebApiCompatShimTest", "test\Microsoft.AspNet.Mvc.WebApiCompatShimTest\Microsoft.AspNet.Mvc.WebApiCompatShimTest.kproj", "{5DE8E4D9-AACD-4B5F-819F-F091383FB996}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.TestConfiguration", "test\WebSites\Microsoft.AspNet.Mvc.TestConfiguration\Microsoft.AspNet.Mvc.TestConfiguration.kproj", "{680D75ED-601F-4D86-B01B-1072D0C31B8C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -216,6 +218,18 @@ Global {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|Mixed Platforms.Build.0 = Release|Any CPU {5DE8E4D9-AACD-4B5F-819F-F091383FB996}.Release|x86.ActiveCfg = Release|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|x86.ActiveCfg = Debug|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Debug|x86.Build.0 = Debug|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|Any CPU.Build.0 = Release|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|x86.ActiveCfg = Release|Any CPU + {680D75ED-601F-4D86-B01B-1072D0C31B8C}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -237,5 +251,6 @@ Global {E69FD235-2042-43A4-9970-59CB29955B4E} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {23D30B8C-04B1-4577-A604-ED27EA1E4A0E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {5DE8E4D9-AACD-4B5F-819F-F091383FB996} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {680D75ED-601F-4D86-B01B-1072D0C31B8C} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} EndGlobalSection EndGlobal From 95ae4cb3a58c3ad83e61c2dbfb27820bf98b9516 Mon Sep 17 00:00:00 2001 From: Bruce Bowyer-Smyth Date: Thu, 1 Jan 2015 15:39:48 +1000 Subject: [PATCH 035/118] Use optimal StringComparison for symbol strings --- .../ApplicationModels/AttributeRouteModel.cs | 20 +++++++++---------- .../Formatters/OutputFormatter.cs | 2 +- .../StringWithQualityHeaderValueComparer.cs | 4 ++-- .../DefaultViewComponentSelector.cs | 2 +- .../DefaultContentNegotiator.cs | 2 +- .../StringWithQualityHeaderValueComparer.cs | 4 ++-- 6 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs index 199ad7f8a2..626f5aa35f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs @@ -124,7 +124,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels return right; } - if (left.EndsWith("/", StringComparison.OrdinalIgnoreCase)) + if (left.EndsWith("/", StringComparison.Ordinal)) { return left + right; } @@ -136,16 +136,16 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels private static bool IsOverridePattern(string template) { return template != null && - (template.StartsWith("~/", StringComparison.OrdinalIgnoreCase) || - template.StartsWith("/", StringComparison.OrdinalIgnoreCase)); + (template.StartsWith("~/", StringComparison.Ordinal) || + template.StartsWith("/", StringComparison.Ordinal)); } private static bool IsEmptyLeftSegment(string template) { return template == null || - template.Equals(string.Empty, StringComparison.OrdinalIgnoreCase) || - template.Equals("~/", StringComparison.OrdinalIgnoreCase) || - template.Equals("/", StringComparison.OrdinalIgnoreCase); + template.Equals(string.Empty, StringComparison.Ordinal) || + template.Equals("~/", StringComparison.Ordinal) || + template.Equals("/", StringComparison.Ordinal); } private static string CleanTemplate(string result) @@ -158,17 +158,17 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels // This is an invalid combined template, so we don't want to // accidentally clean it and produce a valid template. For that // reason we ignore the clean up process for it. - if (result.Equals("//", StringComparison.OrdinalIgnoreCase)) + if (result.Equals("//", StringComparison.Ordinal)) { return result; } var startIndex = 0; - if (result.StartsWith("/", StringComparison.OrdinalIgnoreCase)) + if (result.StartsWith("/", StringComparison.Ordinal)) { startIndex = 1; } - else if (result.StartsWith("~/", StringComparison.OrdinalIgnoreCase)) + else if (result.StartsWith("~/", StringComparison.Ordinal)) { startIndex = 2; } @@ -180,7 +180,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels } var subStringLength = result.Length - startIndex; - if (result.EndsWith("/", StringComparison.OrdinalIgnoreCase)) + if (result.EndsWith("/", StringComparison.Ordinal)) { subStringLength--; } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs index 357a002296..474871f2bb 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs @@ -222,7 +222,7 @@ namespace Microsoft.AspNet.Mvc supportedEncoding => charset.Equals(supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase) || - charset.Equals("*", StringComparison.OrdinalIgnoreCase)); + charset.Equals("*", StringComparison.Ordinal)); if (encoding != null) { return encoding; diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/StringWithQualityHeaderValueComparer.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/StringWithQualityHeaderValueComparer.cs index f628aff6a2..f8579d5189 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/StringWithQualityHeaderValueComparer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/StringWithQualityHeaderValueComparer.cs @@ -56,11 +56,11 @@ namespace Microsoft.AspNet.Mvc if (!String.Equals(stringWithQuality1.Value, stringWithQuality2.Value, StringComparison.OrdinalIgnoreCase)) { - if (String.Equals(stringWithQuality1.Value, "*", StringComparison.OrdinalIgnoreCase)) + if (String.Equals(stringWithQuality1.Value, "*", StringComparison.Ordinal)) { return -1; } - else if (String.Equals(stringWithQuality2.Value, "*", StringComparison.OrdinalIgnoreCase)) + else if (String.Equals(stringWithQuality2.Value, "*", StringComparison.Ordinal)) { return 1; } diff --git a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/DefaultViewComponentSelector.cs b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/DefaultViewComponentSelector.cs index c3b71029bb..5a3c5a661f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/DefaultViewComponentSelector.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/DefaultViewComponentSelector.cs @@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Mvc }; Debug.Assert(!string.IsNullOrEmpty(candidate.FullName)); - var separatorIndex = candidate.FullName.LastIndexOf("."); + var separatorIndex = candidate.FullName.LastIndexOf('.'); if (separatorIndex >= 0) { candidate.ShortName = candidate.FullName.Substring(separatorIndex + 1); diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/DefaultContentNegotiator.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/DefaultContentNegotiator.cs index 22500f4d1a..fe95f0955e 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/DefaultContentNegotiator.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/DefaultContentNegotiator.cs @@ -273,7 +273,7 @@ namespace System.Net.Http.Formatting Encoding encoding = supportedEncodings[i]; if (encoding != null && acceptCharset.Quality != FormattingUtilities.NoMatch && (acceptCharset.Value.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase) || - acceptCharset.Value.Equals("*", StringComparison.OrdinalIgnoreCase))) + acceptCharset.Value.Equals("*", StringComparison.Ordinal))) { return encoding; } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/StringWithQualityHeaderValueComparer.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/StringWithQualityHeaderValueComparer.cs index 2c6d4f090e..e210572a59 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/StringWithQualityHeaderValueComparer.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/StringWithQualityHeaderValueComparer.cs @@ -60,11 +60,11 @@ namespace System.Net.Http.Formatting if (!String.Equals(stringWithQuality1.Value, stringWithQuality2.Value, StringComparison.OrdinalIgnoreCase)) { - if (String.Equals(stringWithQuality1.Value, "*", StringComparison.OrdinalIgnoreCase)) + if (String.Equals(stringWithQuality1.Value, "*", StringComparison.Ordinal)) { return -1; } - else if (String.Equals(stringWithQuality2.Value, "*", StringComparison.OrdinalIgnoreCase)) + else if (String.Equals(stringWithQuality2.Value, "*", StringComparison.Ordinal)) { return 1; } From af9e9f9113fb88d443c69925d5d77d7b3e450281 Mon Sep 17 00:00:00 2001 From: Bruce Bowyer-Smyth Date: Thu, 1 Jan 2015 16:16:29 +1000 Subject: [PATCH 036/118] Remove boxing of chars when concatenated with strings --- .../ApplicationModels/AttributeRouteModel.cs | 2 +- .../Directives/InjectChunkMerger.cs | 2 +- .../Directives/SetBaseTypeChunkMerger.cs | 2 +- src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunkVisitor.cs | 4 ++-- src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs | 2 +- test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs index 626f5aa35f..bd13b89380 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/AttributeRouteModel.cs @@ -130,7 +130,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels } // Both templates contain some text. - return left + '/' + right; + return left + "/" + right; } private static bool IsOverridePattern(string template) diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/InjectChunkMerger.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/InjectChunkMerger.cs index 70ab3a878c..ac03716cf3 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/InjectChunkMerger.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/InjectChunkMerger.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives /// The model type to be used to replace <TModel> tokens. public InjectChunkMerger([NotNull] string modelType) { - _modelType = '<' + modelType + '>'; + _modelType = "<" + modelType + ">"; } /// diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/SetBaseTypeChunkMerger.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/SetBaseTypeChunkMerger.cs index 72723c8866..8d3bcb8f1c 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/SetBaseTypeChunkMerger.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/SetBaseTypeChunkMerger.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives /// The type name of the model used by default. public SetBaseTypeChunkMerger(string modelType) { - _modelType = '<' + modelType + '>'; + _modelType = "<" + modelType + ">"; } /// diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunkVisitor.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunkVisitor.cs index 5cd8548fb5..3fb71b5029 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunkVisitor.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/InjectChunkVisitor.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Mvc.Razor [NotNull] string activateAttributeName) : base(writer, context) { - _activateAttribute = '[' + activateAttributeName + ']'; + _activateAttribute = "[" + activateAttributeName + "]"; } public List InjectChunks @@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Mvc.Razor var code = string.IsNullOrEmpty(chunk.MemberName) ? chunk.TypeName : - chunk.TypeName + ' ' + chunk.MemberName; + chunk.TypeName + " " + chunk.MemberName; var csharpVisitor = new CSharpCodeVisitor(Writer, Context); csharpVisitor.CreateExpressionCodeMapping(code, chunk); Writer.WriteLine("{ get; private set; }"); diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs index 0007518f79..450c96c5e5 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs @@ -60,7 +60,7 @@ namespace Microsoft.AspNet.Mvc.Razor _baseType = BaseType; TagHelperDescriptorResolver = new TagHelperDescriptorResolver(); - DefaultBaseClass = BaseType + '<' + DefaultModel + '>'; + DefaultBaseClass = BaseType + "<" + DefaultModel + ">"; DefaultNamespace = "Asp"; // Enable instrumentation by default to allow precompiled views to work with BrowserLink. EnableInstrumentation = true; diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs index 623e961b3d..8160aff7cb 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RoutingTests.cs @@ -1422,7 +1422,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests public override string ToString() { - return Url + '?' + string.Join("&", Values.Select(kvp => kvp.Key + '=' + kvp.Value)); + return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); } public static implicit operator string (LinkBuilder builder) From 391816eb7141569ad8bb70b23e3fd2fa327dbdcc Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 30 Dec 2014 16:26:08 -0800 Subject: [PATCH 037/118] [Fixes #1713]WebApiCompatShim doesn't work with Transfer Encoding: chunked --- .../HttpResponseMessageOutputFormatter.cs | 15 +++ .../WebApiCompatShimBasicTest.cs | 36 ++++-- ...HttpResponseMessageOutputFormatterTests.cs | 109 +++++++++++++++++- 3 files changed, 147 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs index 655e926ad5..3c12f904d2 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs @@ -51,6 +51,15 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim } var responseHeaders = responseMessage.Headers; + + // Ignore the Transfer-Encoding header if it is just "chunked". + // We let the host decide about whether the response should be chunked or not. + if (responseHeaders.TransferEncodingChunked == true && + responseHeaders.TransferEncoding.Count == 1) + { + responseHeaders.TransferEncoding.Clear(); + } + foreach (var header in responseHeaders) { response.Headers.AppendValues(header.Key, header.Value.ToArray()); @@ -59,6 +68,12 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim if (responseMessage.Content != null) { var contentHeaders = responseMessage.Content.Headers; + + // Copy the response content headers only after ensuring they are complete. + // We ask for Content-Length first because HttpContent lazily computes this + // and only afterwards writes the value into the content headers. + var unused = contentHeaders.ContentLength; + foreach (var header in contentHeaders) { response.Headers.AppendValues(header.Key, header.Value.ToArray()); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs index 532fe2de37..181faded56 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Formatting; @@ -276,29 +277,44 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests } [Fact] - public async Task ApiController_ResponseReturned_Chunked() + public async Task ApiController_ExplicitChunkedEncoding_IsIgnored() { - // Arrange + // Arrange var server = TestServer.Create(_provider, _app); var client = server.CreateClient(); var expected = "POST Hello, HttpResponseMessage world!"; - // Act - var response = await client.PostAsync( - "http://localhost/api/Blog/HttpRequestMessage/EchoWithResponseMessageChunked", - new StringContent("Hello, HttpResponseMessage world!")); - var content = await response.Content.ReadAsStringAsync(); + // Act + var request = new HttpRequestMessage(); + request.Method = HttpMethod.Post; + request.RequestUri = new Uri("http://localhost/api/Blog/HttpRequestMessage/EchoWithResponseMessageChunked"); + request.Content = new StringContent("Hello, HttpResponseMessage world!"); - // Assert + // HttpClient buffers the response by default and this would set the Content-Length header and so + // this will not provide us accurate information as to whether the server set the header or + // the client. So here we explicitly mention to only read the headers and not the body. + var response = await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + + // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(expected, content); + Assert.NotNull(response.Content); + Assert.NotNull(response.Content.Headers.ContentLength); + Assert.Null(response.Headers.TransferEncodingChunked); + + // When HttpClient by default reads and buffers the resposne body, it diposes the + // response stream for us. But since we are reading the content explicitly, we need + // to close it. + var responseStream = await response.Content.ReadAsStreamAsync(); + using (var streamReader = new StreamReader(responseStream)) + { + Assert.Equal(expected, streamReader.ReadToEnd()); + } IEnumerable values; Assert.True(response.Headers.TryGetValues("X-Test", out values)); Assert.Equal(new string[] { "Hello!" }, values); - Assert.Equal(true, response.Headers.TransferEncodingChunked); } [Theory] diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs index c90664d31c..6da3aace49 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs @@ -4,9 +4,12 @@ #if !ASPNETCORE50 using System; +using System.Linq; using System.IO; using System.Net.Http; +using System.Text; using System.Threading.Tasks; +using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc.WebApiCompatShim; using Microsoft.AspNet.PipelineCore; using Moq; @@ -26,7 +29,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShimTest streamContent.Protected().Setup("Dispose", true).Verifiable(); var httpResponseMessage = new HttpResponseMessage(); httpResponseMessage.Content = streamContent.Object; - var outputFormatterContext = GetOutputFormatterContext(httpResponseMessage, typeof(HttpResponseMessage)); + var outputFormatterContext = GetOutputFormatterContext( + httpResponseMessage, + typeof(HttpResponseMessage), + new DefaultHttpContext()); // Act await formatter.WriteAsync(outputFormatterContext); @@ -35,13 +41,110 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShimTest streamContent.Protected().Verify("Dispose", Times.Once(), true); } - private OutputFormatterContext GetOutputFormatterContext(object outputValue, Type outputType) + [Fact] + public async Task ExplicitlySet_ChunkedEncodingFlag_IsIgnored() + { + // Arrange + var httpResponseMessage = new HttpResponseMessage(); + httpResponseMessage.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Hello, World"))); + httpResponseMessage.Headers.TransferEncodingChunked = true; + + var httpContext = new DefaultHttpContext(); + var formatter = new HttpResponseMessageOutputFormatter(); + var outputFormatterContext = GetOutputFormatterContext( + httpResponseMessage, + typeof(HttpResponseMessage), + httpContext); + // Act + await formatter.WriteAsync(outputFormatterContext); + + // Assert + Assert.False(httpContext.Response.Headers.ContainsKey("Transfer-Encoding")); + Assert.NotNull(httpContext.Response.ContentLength); + } + + [Fact] + public async Task ExplicitlySet_ChunkedEncodingHeader_IsIgnored() + { + // Arrange + var transferEncodingHeaderKey = "Transfer-Encoding"; + var httpResponseMessage = new HttpResponseMessage(); + httpResponseMessage.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Hello, World"))); + httpResponseMessage.Headers.Add(transferEncodingHeaderKey, "chunked"); + + var httpContext = new DefaultHttpContext(); + var formatter = new HttpResponseMessageOutputFormatter(); + var outputFormatterContext = GetOutputFormatterContext( + httpResponseMessage, + typeof(HttpResponseMessage), + httpContext); + // Act + await formatter.WriteAsync(outputFormatterContext); + + // Assert + Assert.False(httpContext.Response.Headers.ContainsKey(transferEncodingHeaderKey)); + Assert.NotNull(httpContext.Response.ContentLength); + } + + [Fact] + public async Task ExplicitlySet_MultipleEncodings_ChunkedNotIgnored() + { + // Arrange + var transferEncodingHeaderKey = "Transfer-Encoding"; + var httpResponseMessage = new HttpResponseMessage(); + httpResponseMessage.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Hello, World"))); + httpResponseMessage.Headers.Add(transferEncodingHeaderKey, new[] { "identity", "chunked" }); + + var httpContext = new DefaultHttpContext(); + var formatter = new HttpResponseMessageOutputFormatter(); + var outputFormatterContext = GetOutputFormatterContext( + httpResponseMessage, + typeof(HttpResponseMessage), + httpContext); + // Act + await formatter.WriteAsync(outputFormatterContext); + + // Assert + Assert.True(httpContext.Response.Headers.ContainsKey(transferEncodingHeaderKey)); + Assert.Equal(new string[] { "identity", "chunked" }, + httpContext.Response.Headers.GetValues(transferEncodingHeaderKey)); + Assert.NotNull(httpContext.Response.ContentLength); + } + + [Fact] + public async Task ExplicitlySet_MultipleEncodingsUsingChunkedFlag_ChunkedNotIgnored() + { + // Arrange + var transferEncodingHeaderKey = "Transfer-Encoding"; + var httpResponseMessage = new HttpResponseMessage(); + httpResponseMessage.Content = new StreamContent(new MemoryStream(Encoding.UTF8.GetBytes("Hello, World"))); + httpResponseMessage.Headers.Add(transferEncodingHeaderKey, new[] { "identity" }); + httpResponseMessage.Headers.TransferEncodingChunked = true; + + var httpContext = new DefaultHttpContext(); + var formatter = new HttpResponseMessageOutputFormatter(); + var outputFormatterContext = GetOutputFormatterContext( + httpResponseMessage, + typeof(HttpResponseMessage), + httpContext); + // Act + await formatter.WriteAsync(outputFormatterContext); + + // Assert + Assert.True(httpContext.Response.Headers.ContainsKey(transferEncodingHeaderKey)); + Assert.Equal(new string[] { "identity", "chunked" }, + httpContext.Response.Headers.GetValues(transferEncodingHeaderKey)); + Assert.NotNull(httpContext.Response.ContentLength); + } + + private OutputFormatterContext GetOutputFormatterContext(object outputValue, Type outputType, + HttpContext httpContext) { return new OutputFormatterContext { Object = outputValue, DeclaredType = outputType, - ActionContext = new ActionContext(new DefaultHttpContext(), routeData: null, actionDescriptor: null) + ActionContext = new ActionContext(httpContext, routeData: null, actionDescriptor: null) }; } } From bc6833ee722a4f0b960f6603fc7fb6030d8ce620 Mon Sep 17 00:00:00 2001 From: Troy Dai Date: Tue, 6 Jan 2015 11:01:20 -0800 Subject: [PATCH 038/118] Fix build break --- .../Compilation/SyntaxTreeGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/SyntaxTreeGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/SyntaxTreeGenerator.cs index 0453ba051d..606d29376d 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/SyntaxTreeGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/SyntaxTreeGenerator.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNet.Mvc.Razor { return new CSharpParseOptions( languageVersion: compilationSettings.LanguageVersion, - preprocessorSymbols: compilationSettings.Defines.AsImmutable()); + preprocessorSymbols: compilationSettings.Defines); } } } \ No newline at end of file From 51567194dce7d45c985856e4d32ab443a5356843 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Mon, 5 Jan 2015 13:18:34 -0800 Subject: [PATCH 039/118] deleted duplicate action results in WebapiCompatShim --- .../ActionResults/CreatedAtActionResult.cs | 8 +- .../ActionResults/CreatedAtRouteResult.cs | 3 +- .../ActionResults/BadRequestResult.cs | 22 --- .../CreatedAtRouteNegotiatedContentResult.cs | 65 --------- .../CreatedNegotiatedContentResult.cs | 52 ------- .../ActionResults/NegotiatedContentResult.cs | 7 +- .../ApiController.cs | 52 ++++--- .../ActionResultTests.cs | 6 +- .../ActionResults/BadRequestResultTest.cs | 28 ---- ...eatedAtRouteNegotiatedContentResultTest.cs | 130 ------------------ .../CreatedNegotiatedContentResultTest.cs | 111 --------------- .../ApiControllerTest.cs | 27 ++-- 12 files changed, 52 insertions(+), 459 deletions(-) delete mode 100644 src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/BadRequestResult.cs delete mode 100644 src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/CreatedAtRouteNegotiatedContentResult.cs delete mode 100644 src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/CreatedNegotiatedContentResult.cs delete mode 100644 test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ActionResults/BadRequestResultTest.cs delete mode 100644 test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ActionResults/CreatedAtRouteNegotiatedContentResultTest.cs delete mode 100644 test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ActionResults/CreatedNegotiatedContentResultTest.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs index 764b9bbc62..3e968f33e2 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs @@ -56,9 +56,15 @@ namespace Microsoft.AspNet.Mvc /// protected override void OnFormatting([NotNull] ActionContext context) { + var request = context.HttpContext.Request; var urlHelper = UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService(); - var url = urlHelper.Action(ActionName, ControllerName, RouteValues); + var url = urlHelper.Action( + ActionName, + ControllerName, + RouteValues, + request.Scheme, + request.Host.ToUriComponent()); if (string.IsNullOrEmpty(url)) { diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs index d1e99b447d..9020978d00 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs @@ -59,9 +59,10 @@ namespace Microsoft.AspNet.Mvc /// protected override void OnFormatting([NotNull] ActionContext context) { + var request = context.HttpContext.Request; var urlHelper = UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService(); - var url = urlHelper.RouteUrl(RouteName, RouteValues); + var url = urlHelper.RouteUrl(RouteName, RouteValues, request.Scheme, request.Host.ToUriComponent()); if (string.IsNullOrEmpty(url)) { diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/BadRequestResult.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/BadRequestResult.cs deleted file mode 100644 index 447aa8e471..0000000000 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/BadRequestResult.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Net; -using Microsoft.AspNet.Mvc; - -namespace System.Web.Http -{ - /// - /// An action result that returns an empty response. - /// - public class BadRequestResult : HttpStatusCodeResult - { - /// - /// Initializes a new instance of the class. - /// - public BadRequestResult() - : base((int)HttpStatusCode.BadRequest) - { - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/CreatedAtRouteNegotiatedContentResult.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/CreatedAtRouteNegotiatedContentResult.cs deleted file mode 100644 index 28db5cabef..0000000000 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/CreatedAtRouteNegotiatedContentResult.cs +++ /dev/null @@ -1,65 +0,0 @@ -// 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.Net; -using System.Threading.Tasks; -using Microsoft.AspNet.Mvc; -using Microsoft.Framework.DependencyInjection; -using ShimResources = Microsoft.AspNet.Mvc.WebApiCompatShim.Resources; - -namespace System.Web.Http -{ - /// - /// Represents an action result that performs route generation and content negotiation and returns a - /// response when content negotiation succeeds. - /// - /// The type of content in the entity body. - public class CreatedAtRouteNegotiatedContentResult : NegotiatedContentResult - { - /// - /// Initializes a new instance of the class with the - /// values provided. - /// - /// The name of the route to use for generating the URL. - /// The route data to use for generating the URL. - /// The content value to negotiate and format in the entity body. - /// The formatters to use to negotiate and format the content. - public CreatedAtRouteNegotiatedContentResult( - [NotNull] string routeName, - [NotNull] IDictionary routeValues, - [NotNull] T content) - : base(HttpStatusCode.Created, content) - { - RouteName = routeName; - RouteValues = routeValues; - } - - /// - /// Gets the name of the route to use for generating the URL. - /// - public string RouteName { get; private set; } - - /// - /// Gets the route data to use for generating the URL. - /// - public IDictionary RouteValues { get; private set; } - - /// - public override Task ExecuteResultAsync(ActionContext context) - { - var request = context.HttpContext.Request; - var urlHelper = context.HttpContext.RequestServices.GetService(); - - var url = urlHelper.RouteUrl(RouteName, RouteValues, request.Scheme, request.Host.ToUriComponent()); - if (url == null) - { - throw new InvalidOperationException(ShimResources.FormatCreatedAtRoute_RouteFailed(RouteName)); - } - - context.HttpContext.Response.Headers.Add("Location", new string[] { url }); - - return base.ExecuteResultAsync(context); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/CreatedNegotiatedContentResult.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/CreatedNegotiatedContentResult.cs deleted file mode 100644 index ccd96f5380..0000000000 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/CreatedNegotiatedContentResult.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNet.Mvc; - -namespace System.Web.Http -{ - /// - /// Represents an action result that performs route generation and content negotiation and returns a - /// response when content negotiation succeeds. - /// - /// The type of content in the entity body. - public class CreatedNegotiatedContentResult : NegotiatedContentResult - { - /// - /// Initializes a new instance of the class with the values - /// provided. - /// - /// The location at which the content has been created. - /// The content value to negotiate and format in the entity body. - public CreatedNegotiatedContentResult(Uri location, T content) - : base(HttpStatusCode.Created, content) - { - Location = location; - } - - /// - /// Gets the location at which the content has been created. - /// - public Uri Location { get; private set; } - - /// - public override Task ExecuteResultAsync(ActionContext context) - { - string location; - if (Location.IsAbsoluteUri) - { - location = Location.AbsoluteUri; - } - else - { - location = Location.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); - } - - context.HttpContext.Response.Headers.Add("Location", new string[] { location }); - - return base.ExecuteResultAsync(context); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/NegotiatedContentResult.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/NegotiatedContentResult.cs index bed9f4109a..c63ca5c707 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/NegotiatedContentResult.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ActionResults/NegotiatedContentResult.cs @@ -21,15 +21,10 @@ namespace System.Web.Http public NegotiatedContentResult(HttpStatusCode statusCode, T content) : base(content) { - StatusCode = statusCode; + StatusCode = (int)statusCode; Content = content; } - /// - /// Gets the HTTP status code for the response message. - /// - public HttpStatusCode StatusCode { get; private set; } - /// /// Gets the content value to negotiate and format in the entity body. /// diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs index 8e040f4ec4..54ff8becf5 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.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.Net; using System.Net.Http; using System.Security.Principal; @@ -10,7 +9,6 @@ using Microsoft.AspNet.Http; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.WebApiCompatShim; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; using Newtonsoft.Json; @@ -146,54 +144,54 @@ namespace System.Web.Http } /// - /// Creates a (201 Created) with the specified values. + /// Creates a (201 Created) with the specified values. /// - /// The type of content in the entity body. /// /// The location at which the content has been created. Must be a relative or absolute URL. /// - /// The content value to negotiate and format in the entity body. - /// A with the specified values. + /// The content value to format in the entity body. + /// A with the specified values. [NonAction] - public virtual CreatedNegotiatedContentResult Created([NotNull] string location, [NotNull] T content) + public virtual CreatedResult Created([NotNull] string location, object content) { - return Created(new Uri(location, UriKind.RelativeOrAbsolute), content); + return new CreatedResult(location, content); } /// - /// Creates a (201 Created) with the specified values. + /// Creates a (201 Created) with the specified values. /// - /// The type of content in the entity body. /// The location at which the content has been created. - /// The content value to negotiate and format in the entity body. - /// A with the specified values. + /// The content value to format in the entity body. + /// A with the specified values. [NonAction] - public virtual CreatedNegotiatedContentResult Created([NotNull] Uri location, [NotNull] T content) + public virtual CreatedResult Created([NotNull] Uri uri, object content) { - return new CreatedNegotiatedContentResult(location, content); + string location; + if (uri.IsAbsoluteUri) + { + location = uri.AbsoluteUri; + } + else + { + location = uri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); + } + return Created(location, content); } /// - /// Creates a (201 Created) with the specified values. + /// Creates a (201 Created) with the specified values. /// - /// The type of content in the entity body. /// The name of the route to use for generating the URL. /// The route data to use for generating the URL. - /// The content value to negotiate and format in the entity body. - /// A with the specified values. + /// The content value to format in the entity body. + /// A with the specified values. [NonAction] - public virtual CreatedAtRouteNegotiatedContentResult CreatedAtRoute( + public virtual CreatedAtRouteResult CreatedAtRoute( [NotNull] string routeName, object routeValues, - [NotNull] T content) + object content) { - var values = routeValues as IDictionary; - if (values == null) - { - values = new RouteValueDictionary(routeValues); - } - - return new CreatedAtRouteNegotiatedContentResult(routeName, values, content); + return new CreatedAtRouteResult(routeName, routeValues, content); } /// (MockBehavior.Strict); - urlHelper - .Setup(u => u.RouteUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns("http://contoso.com/api/Products/5"); - - var httpContext = new DefaultHttpContext(); - httpContext.RequestServices = CreateServices(urlHelper.Object); - - var stream = new MemoryStream(); - httpContext.Response.Body = stream; - - var context = new ActionContext(new RouteContext(httpContext), new ActionDescriptor()); - var result = new CreatedAtRouteNegotiatedContentResult( - "api_route", - new RouteValueDictionary(new { controller = "Products", id = 5 }), - new Product()); - - // Act - await result.ExecuteResultAsync(context); - - // Assert - Assert.Equal(201, context.HttpContext.Response.StatusCode); - } - - [Fact] - public async Task CreatedAtRouteNegotiatedContentResult_SetsLocation() - { - // Arrange - var urlHelper = new Mock(MockBehavior.Strict); - urlHelper - .Setup(u => u.RouteUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns("http://contoso.com/api/Products/5"); - - var httpContext = new DefaultHttpContext(); - httpContext.RequestServices = CreateServices(urlHelper.Object); - - var stream = new MemoryStream(); - httpContext.Response.Body = stream; - - var context = new ActionContext(new RouteContext(httpContext), new ActionDescriptor()); - var result = new CreatedAtRouteNegotiatedContentResult( - "api_route", - new RouteValueDictionary(new { controller = "Products", id = 5 }), - new Product()); - - // Act - await result.ExecuteResultAsync(context); - - // Assert - Assert.Equal("http://contoso.com/api/Products/5", httpContext.Response.Headers["Location"]); - } - - [Fact] - public async Task CreatedAtRouteNegotiatedContentResult_Fails() - { - // Arrange - var urlHelper = new Mock(MockBehavior.Strict); - urlHelper - .Setup(u => u.RouteUrl(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())) - .Returns((string)null); - - var httpContext = new DefaultHttpContext(); - httpContext.RequestServices = CreateServices(urlHelper.Object); - - var stream = new MemoryStream(); - httpContext.Response.Body = stream; - - var context = new ActionContext(new RouteContext(httpContext), new ActionDescriptor()); - var result = new CreatedAtRouteNegotiatedContentResult( - "api_route", - new RouteValueDictionary(new { controller = "Products", id = 5 }), - new Product()); - - // Act - var ex = await Assert.ThrowsAsync(async () => await result.ExecuteResultAsync(context)); - - // Assert - Assert.Equal("Failed to generate a URL using route 'api_route'.", ex.Message); - } - - private IServiceProvider CreateServices(IUrlHelper urlHelper) - { - var services = new Mock(MockBehavior.Strict); - - services - .Setup(s => s.GetService(typeof(IUrlHelper))) - .Returns(urlHelper); - - var formatters = new Mock(MockBehavior.Strict); - formatters - .SetupGet(f => f.OutputFormatters) - .Returns(new List() { new JsonOutputFormatter(), }); - - services - .Setup(s => s.GetService(typeof(IOutputFormattersProvider))) - .Returns(formatters.Object); - - return services.Object; - } - - private class Product - { - public int Id { get; set; } - - public string Name { get; set; } - }; - } -} -#endif \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ActionResults/CreatedNegotiatedContentResultTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ActionResults/CreatedNegotiatedContentResultTest.cs deleted file mode 100644 index ee2312c856..0000000000 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ActionResults/CreatedNegotiatedContentResultTest.cs +++ /dev/null @@ -1,111 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -#if ASPNET50 -using System.Collections.Generic; -using System.IO; -using System.Threading.Tasks; -using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.PipelineCore; -using Microsoft.AspNet.Routing; -using Moq; -using Xunit; - -namespace System.Web.Http -{ - public class CreatedNegotiatedContentResultTest - { - [Fact] - public async Task CreatedNegotiatedContentResult_SetsStatusCode() - { - // Arrange - var httpContext = new DefaultHttpContext(); - httpContext.RequestServices = CreateServices(); - - var stream = new MemoryStream(); - httpContext.Response.Body = stream; - - var uri = new Uri("http://contoso.com"); - - var context = new ActionContext(new RouteContext(httpContext), new ActionDescriptor()); - var result = new CreatedNegotiatedContentResult(uri, new Product()); - - // Act - await result.ExecuteResultAsync(context); - - // Assert - Assert.Equal(201, context.HttpContext.Response.StatusCode); - } - - [Fact] - public async Task CreatedNegotiatedContentResult_SetsLocation_Uri() - { - // Arrange - var httpContext = new DefaultHttpContext(); - httpContext.RequestServices = CreateServices(); - - var stream = new MemoryStream(); - httpContext.Response.Body = stream; - - var uri = new Uri("http://contoso.com"); - - var context = new ActionContext(new RouteContext(httpContext), new ActionDescriptor()); - var result = new CreatedNegotiatedContentResult(uri, new Product()); - - // Act - await result.ExecuteResultAsync(context); - - // Assert - Assert.Equal("http://contoso.com/", httpContext.Response.Headers["Location"]); - } - - [Theory] - [InlineData("http://contoso.com/Api/Products")] - [InlineData("/Api/Products")] - [InlineData("Products")] - public async Task CreatedNegotiatedContentResult_SetsLocation_String(string uri) - { - // Arrange - var httpContext = new DefaultHttpContext(); - httpContext.RequestServices = CreateServices(); - - var stream = new MemoryStream(); - httpContext.Response.Body = stream; - - var context = new ActionContext(new RouteContext(httpContext), new ActionDescriptor()); - var result = new CreatedNegotiatedContentResult( - new Uri(uri, UriKind.RelativeOrAbsolute), - new Product()); - - // Act - await result.ExecuteResultAsync(context); - - // Assert - Assert.Equal(uri, httpContext.Response.Headers["Location"]); - } - - private IServiceProvider CreateServices() - { - var services = new Mock(MockBehavior.Strict); - - var formatters = new Mock(MockBehavior.Strict); - formatters - .SetupGet(f => f.OutputFormatters) - .Returns(new List() { new JsonOutputFormatter(), }); - - services - .Setup(s => s.GetService(typeof(IOutputFormattersProvider))) - .Returns(formatters.Object); - - return services.Object; - } - - private class Product - { - public int Id { get; set; } - - public string Name { get; set; } - }; - } -} -#endif \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs index 996baeffb3..41eef06657 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Security.Claims; @@ -104,16 +105,16 @@ namespace System.Web.Http // Arrange var controller = new ConcreteApiController(); - var uri = new Uri("http://contoso.com"); + var uri = new Uri("http://contoso.com/"); var product = new Product(); // Act var result = controller.Created(uri, product); // Assert - var created = Assert.IsType>(result); - Assert.Same(product, created.Content); - Assert.Same(uri, created.Location); + var created = Assert.IsType(result); + Assert.Same(product, created.Value); + Assert.Equal(uri.OriginalString, created.Location); } [Theory] @@ -131,9 +132,9 @@ namespace System.Web.Http var result = controller.Created(uri, product); // Assert - var created = Assert.IsType>(result); - Assert.Same(product, created.Content); - Assert.Equal(uri, created.Location.OriginalString); + var created = Assert.IsType(result); + Assert.Same(product, created.Value); + Assert.Equal(uri, created.Location); } [Fact] @@ -148,8 +149,8 @@ namespace System.Web.Http var result = controller.CreatedAtRoute("api_route", new { controller = "Products" }, product); // Assert - var created = Assert.IsType>(result); - Assert.Same(product, created.Content); + var created = Assert.IsType(result); + Assert.Same(product, created.Value); Assert.Equal("api_route", created.RouteName); Assert.Equal("Products", created.RouteValues["controller"]); } @@ -167,11 +168,11 @@ namespace System.Web.Http var result = controller.CreatedAtRoute("api_route", values, product); // Assert - var created = Assert.IsType>(result); - Assert.Same(product, created.Content); + var created = Assert.IsType(result); + Assert.Same(product, created.Value); Assert.Equal("api_route", created.RouteName); Assert.Equal("Products", created.RouteValues["controller"]); - Assert.Same(values, created.RouteValues); + Assert.Equal>(values, created.RouteValues); } [Fact] @@ -200,7 +201,7 @@ namespace System.Web.Http // Assert var contentResult = Assert.IsType>(result); - Assert.Equal(HttpStatusCode.Found, contentResult.StatusCode); + Assert.Equal((int)HttpStatusCode.Found, contentResult.StatusCode); Assert.Equal(content, contentResult.Value); } From 5262dfd577be0f3e48473a9ddc9a40a000916028 Mon Sep 17 00:00:00 2001 From: sornaks Date: Mon, 5 Jan 2015 16:26:29 -0800 Subject: [PATCH 040/118] Adding SerializableError - a serializable container for the purpose of output conneg. --- .../ActionResults/BadRequestObjectResult.cs | 33 ++++ .../ActionResults/SerializableError.cs | 104 +++++++++++++ src/Microsoft.AspNet.Mvc.Core/Controller.cs | 20 +++ .../Properties/Resources.Designer.cs | 16 ++ src/Microsoft.AspNet.Mvc.Core/Resources.resx | 57 +++---- .../BadRequestObjectResultTests.cs | 35 +++++ .../ActionResults/SerializableErrorTests.cs | 146 ++++++++++++++++++ .../ControllerTests.cs | 32 ++++ .../ActionResultTests.cs | 100 +++++++++++- .../Controllers/HomeController.cs | 33 ++++ .../Controllers/XmlSerializerController.cs | 31 ++++ .../ActionResultsWebSite/Models/DummyClass.cs | 4 +- 12 files changed, 582 insertions(+), 29 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/ActionResults/BadRequestObjectResult.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/ActionResults/SerializableError.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/BadRequestObjectResultTests.cs create mode 100644 test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/SerializableErrorTests.cs create mode 100644 test/WebSites/ActionResultsWebSite/Controllers/HomeController.cs create mode 100644 test/WebSites/ActionResultsWebSite/Controllers/XmlSerializerController.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/BadRequestObjectResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/BadRequestObjectResult.cs new file mode 100644 index 0000000000..e1b3481a5d --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/BadRequestObjectResult.cs @@ -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 +{ + /// + /// An that when executed will produce a Bad Request (400) response. + /// + public class BadRequestObjectResult : ObjectResult + { + /// + /// Creates a new instance. + /// + /// Contains the errors to be returned to the client. + public BadRequestObjectResult(object error) + : base(error) + { + StatusCode = 400; + } + + /// + /// Creates a new instance. + /// + /// containing the validation errors. + public BadRequestObjectResult([NotNull] ModelStateDictionary modelState) + : base(new SerializableError(modelState)) + { + StatusCode = 400; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/SerializableError.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/SerializableError.cs new file mode 100644 index 0000000000..eb37279893 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/SerializableError.cs @@ -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 +{ + /// + /// Defines a serializable container for storing ModelState information. + /// This information is stored as key/value pairs. + /// + [XmlRoot("Error")] + public sealed class SerializableError : Dictionary, IXmlSerializable + { + /// + /// Initializes a new instance of the class. + /// + public SerializableError() + : base(StringComparer.OrdinalIgnoreCase) + { + } + + /// + /// Creates a new instance of . + /// + /// containing the validation errors. + 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); + } + } + } + + // + public XmlSchema GetSchema() + { + return null; + } + + // + 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(); + } + + // + 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(); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index ea16319d0b..53bb774895 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -627,6 +627,26 @@ namespace Microsoft.AspNet.Mvc return new BadRequestResult(); } + /// + /// Creates an that produces a Bad Request (400) response. + /// + /// The created for the response. + [NonAction] + public virtual BadRequestObjectResult HttpBadRequest(object error) + { + return new BadRequestObjectResult(error); + } + + /// + /// Creates an that produces a Bad Request (400) response. + /// + /// The created for the response. + [NonAction] + public virtual BadRequestObjectResult HttpBadRequest([NotNull] ModelStateDictionary modelState) + { + return new BadRequestObjectResult(modelState); + } + /// /// Creates a object that produces a Created (201) response. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index 714664f8df..4b20727882 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -1514,6 +1514,22 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("ViewComponent_AmbiguousTypeMatch_Item"), p0, p1); } + /// + /// The input was not valid. + /// + internal static string SerializableError_DefaultError + { + get { return GetString("SerializableError_DefaultError"); } + } + + /// + /// The input was not valid. + /// + internal static string FormatSerializableError_DefaultError() + { + return GetString("SerializableError_DefaultError"); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index 87355a2b09..b9fb2fe066 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -1,17 +1,17 @@  - @@ -409,4 +409,7 @@ Type: '{0}' - Name: '{1}' + + The input was not valid. + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/BadRequestObjectResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/BadRequestObjectResultTests.cs new file mode 100644 index 0000000000..775a899e13 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/BadRequestObjectResultTests.cs @@ -0,0 +1,35 @@ +// 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 Xunit; +using Microsoft.AspNet.Mvc.ModelBinding; + +namespace Microsoft.AspNet.Mvc +{ + public class BadRequestObjectResultTests + { + [Fact] + public void BadRequestObjectResult_SetsStatusCodeAndValue() + { + // Arrange & Act + var obj = new object(); + var badRequestObjecResult = new BadRequestObjectResult(obj); + + // Assert + Assert.Equal(400, badRequestObjecResult.StatusCode); + Assert.Equal(obj, badRequestObjecResult.Value); + } + + [Fact] + public void BadRequestObjectResult_ModelState_SetsStatusCodeAndValue() + { + // Arrange & Act + var badRequestObjecResult = new BadRequestObjectResult(new ModelStateDictionary()); + + // Assert + Assert.Equal(400, badRequestObjecResult.StatusCode); + var errors = Assert.IsType(badRequestObjecResult.Value); + Assert.Equal(0, errors.Count); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/SerializableErrorTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/SerializableErrorTests.cs new file mode 100644 index 0000000000..67f59c6518 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/SerializableErrorTests.cs @@ -0,0 +1,146 @@ +// 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.Globalization; +using System.IO; +using System.Runtime.Serialization; +using System.Text; +using System.Xml; +using Microsoft.AspNet.Mvc.ModelBinding; +using Xunit; + +namespace Microsoft.AspNet.Mvc +{ + public class SerializableErrorTests + { + [Fact] + public void ConvertsModelState_To_Dictionary() + { + // Arrange + var modelState = new ModelStateDictionary(); + modelState.AddModelError("key1", "Test Error 1"); + modelState.AddModelError("key1", "Test Error 2"); + modelState.AddModelError("key2", "Test Error 3"); + + // Act + var serializableError = new SerializableError(modelState); + + // Assert + var arr = Assert.IsType(serializableError["key1"]); + Assert.Equal("Test Error 1", arr[0]); + Assert.Equal("Test Error 2", arr[1]); + Assert.Equal("Test Error 3", (serializableError["key2"] as string[])[0]); + } + + [Fact] + public void LookupIsCaseInsensitive() + { + // Arrange + var modelState = new ModelStateDictionary(); + modelState.AddModelError("key1", "x"); + + // Act + var serializableError = new SerializableError(modelState); + + // Assert + var arr = Assert.IsType(serializableError["KEY1"]); + Assert.Equal("x", arr[0]); + } + + [Fact] + public void ConvertsModelState_To_Dictionary_AddsDefaultValuesWhenErrorsAreAbsent() + { + // Arrange + var modelState = new ModelStateDictionary(); + modelState.AddModelError("key1", ""); + + // Act + var serializableError = new SerializableError(modelState); + + // Assert + var arr = Assert.IsType(serializableError["key1"]); + Assert.Equal("The input was not valid.", arr[0]); + } + + [Fact] + public void DoesNotThrowOnValidModelState() + { + // Arrange, Act & Assert (does not throw) + new SerializableError(new ModelStateDictionary()); + } + + [Fact] + public void DoesNotAddEntries_IfNoErrorsArePresent() + { + // Arrange + var modelState = new ModelStateDictionary(); + modelState.Add( + "key1", + new ModelState() { Value = new ValueProviderResult("foo", "foo", CultureInfo.InvariantCulture) }); + modelState.Add( + "key2", + new ModelState() { Value = new ValueProviderResult("bar", "bar", CultureInfo.InvariantCulture) }); + + // Act + var serializableError = new SerializableError(modelState); + + // Assert + Assert.Equal(0, serializableError.Count); + } + + [Fact] + public void GetSchema_Returns_Null() + { + // Arrange + var modelState = new ModelStateDictionary(); + // To make modelState invalid. + modelState.AddModelError("key1", "Test Error 1"); + var serializableError = new SerializableError(modelState); + + // Act & Assert + Assert.Null(serializableError.GetSchema()); + } + + [Fact] + public void WriteXml_WritesValidXml() + { + // Arrange + var modelState = new ModelStateDictionary(); + modelState.AddModelError("key1", "Test Error 1"); + modelState.AddModelError("key1", "Test Error 2"); + modelState.AddModelError("key2", "Test Error 3"); + var serializableError = new SerializableError(modelState); + var outputStream = new MemoryStream(); + + // Act + using (var xmlWriter = XmlWriter.Create(outputStream)) + { + var dataContractSerializer = new DataContractSerializer(typeof(SerializableError)); + dataContractSerializer.WriteObject(xmlWriter, serializableError); + } + outputStream.Position = 0; + var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd(); + + // Assert + Assert.Equal("" + + "Test Error 1 Test Error 2Test Error 3", res); + } + + [Fact] + public void ReadXml_ReadsSerializableErrorXml() + { + // Arrange + var serializableErrorXml = "" + + "Test Error 1 Test Error 2Test Error 3"; + var serializer = new DataContractSerializer(typeof(SerializableError)); + + // Act + var errors = (SerializableError)serializer.ReadObject( + new MemoryStream(Encoding.UTF8.GetBytes(serializableErrorXml))); + + // Assert + Assert.Equal("Test Error 1 Test Error 2", errors["key1"]); + Assert.Equal("Test Error 3", errors["key2"]); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs index 55435cfc16..d6ee18e53a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs @@ -669,6 +669,38 @@ namespace Microsoft.AspNet.Mvc.Test Assert.Equal(400, result.StatusCode); } + [Fact] + public void BadRequest_SetsStatusCodeAndValue_Object() + { + // Arrange + var controller = new Controller(); + var obj = new object(); + + // Act + var result = controller.HttpBadRequest(obj); + + // Assert + Assert.IsType(result); + Assert.Equal(400, result.StatusCode); + Assert.Equal(obj, result.Value); + } + + [Fact] + public void BadRequest_SetsStatusCodeAndValue_ModelState() + { + // Arrange + var controller = new Controller(); + + // Act + var result = controller.HttpBadRequest(new ModelStateDictionary()); + + // Assert + Assert.IsType(result); + Assert.Equal(400, result.StatusCode); + var errors = Assert.IsType(result.Value); + Assert.Equal(0, errors.Count); + } + [Theory] [MemberData(nameof(PublicNormalMethodsFromController))] public void NonActionAttribute_IsOnEveryPublicNormalMethodFromController(MethodInfo method) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs index 5e9111a91c..7ff7a5170a 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ActionResultTests.cs @@ -2,10 +2,13 @@ // 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.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; +using System.Xml.Serialization; using ActionResultsWebSite; using Microsoft.AspNet.Builder; using Microsoft.AspNet.TestHost; @@ -17,7 +20,10 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { private readonly IServiceProvider _provider = TestHelper.CreateServices("ActionResultsWebSite"); private readonly Action _app = new Startup().Configure; - + private const string sampleIntError = "The field SampleInt must be between 10 and 100."; + private const string sampleStringError = + "The field SampleString must be a string or array type with a minimum length of '15'."; + [Fact] public async Task BadRequestResult_CanBeReturned() { @@ -181,5 +187,97 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("http://localhost/foo/ActionResultsVerification/GetDummy/1", response.Headers.Location.OriginalString); Assert.Equal("{\"SampleInt\":10,\"SampleString\":\"Foo\"}", await response.Content.ReadAsStringAsync()); } + + [Theory] + [InlineData("http://localhost/Home/Index", + "application/json;charset=utf-8", + "{\"test.SampleInt\":[\"" + sampleIntError + "\"]," + + "\"test.SampleString\":" + + "[\"" + sampleStringError + "\"]}")] + [InlineData("http://localhost/Home/Index", + "application/xml;charset=utf-8", + "" + sampleIntError + "" + + "" + sampleStringError + + "")] + [InlineData("http://localhost/XmlSerializer/GetSerializableError", + "application/xml;charset=utf-8", + "" + sampleIntError + "" + + "" + sampleStringError + + "")] + public async Task SerializableErrorIsReturnedInExpectedFormat(string url, string outputFormat, string output) + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var input = "" + + "" + + "2foo"; + var request = new HttpRequestMessage(HttpMethod.Post, url); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse(outputFormat)); + request.Content = new StringContent(input, Encoding.UTF8, "application/xml"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.Equal(output, await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task SerializableError_CanSerializeNormalObjects() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var input = "" + + "" + + "2foo"; + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Home/GetCustomErrorObject"); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json;charset=utf-8")); + request.Content = new StringContent(input, Encoding.UTF8, "application/xml"); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.Equal("[\"Something went wrong with the model.\"]", + await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task SerializableError_ReadTheReturnedXml() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + var input = "" + + "" + + "20foo"; + + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Home/Index"); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml;charset=utf-8")); + request.Content = new StringContent(input, Encoding.UTF8, "application/xml"); + + // Act + var response = await client.SendAsync(request); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Deserializing Xml content + var serializer = new XmlSerializer(typeof(SerializableError)); + var errors = (SerializableError)serializer.Deserialize( + new MemoryStream(Encoding.UTF8.GetBytes(responseContent))); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + Assert.Equal( + "" + sampleStringError + "", + responseContent); + Assert.Equal(sampleStringError, errors["test.SampleString"]); + } } } \ No newline at end of file diff --git a/test/WebSites/ActionResultsWebSite/Controllers/HomeController.cs b/test/WebSites/ActionResultsWebSite/Controllers/HomeController.cs new file mode 100644 index 0000000000..5fdac34738 --- /dev/null +++ b/test/WebSites/ActionResultsWebSite/Controllers/HomeController.cs @@ -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 System.Collections.Generic; +using Microsoft.AspNet.Mvc; + +namespace ActionResultsWebSite +{ + public class HomeController : Controller + { + public IActionResult Index([FromBody] DummyClass test) + { + if (!ModelState.IsValid) + { + return HttpBadRequest(ModelState); + } + + return Content("Hello World!"); + } + + public IActionResult GetCustomErrorObject([FromBody] DummyClass test) + { + if (!ModelState.IsValid) + { + var errors = new List(); + errors.Add("Something went wrong with the model."); + return new BadRequestObjectResult(errors); + } + + return Content("Hello World!"); + } + } +} \ No newline at end of file diff --git a/test/WebSites/ActionResultsWebSite/Controllers/XmlSerializerController.cs b/test/WebSites/ActionResultsWebSite/Controllers/XmlSerializerController.cs new file mode 100644 index 0000000000..fe500bb0ca --- /dev/null +++ b/test/WebSites/ActionResultsWebSite/Controllers/XmlSerializerController.cs @@ -0,0 +1,31 @@ +// 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 ActionResultsWebSite +{ + public class XmlSerializerController : Controller + { + public IActionResult GetSerializableError([FromBody] DummyClass test) + { + if (!ModelState.IsValid) + { + return HttpBadRequest(ModelState); + } + + return Content("Success!"); + } + + public override void OnActionExecuted(ActionExecutedContext context) + { + var result = context.Result as ObjectResult; + if (result != null) + { + result.Formatters.Add(new XmlSerializerOutputFormatter()); + } + + base.OnActionExecuted(context); + } + } +} \ No newline at end of file diff --git a/test/WebSites/ActionResultsWebSite/Models/DummyClass.cs b/test/WebSites/ActionResultsWebSite/Models/DummyClass.cs index f9a08ad3dd..cf3ea7e377 100644 --- a/test/WebSites/ActionResultsWebSite/Models/DummyClass.cs +++ b/test/WebSites/ActionResultsWebSite/Models/DummyClass.cs @@ -1,4 +1,4 @@ -// 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. using System.ComponentModel.DataAnnotations; @@ -7,8 +7,10 @@ namespace ActionResultsWebSite { public class DummyClass { + [Range(10, 100)] public int SampleInt { get; set; } + [MinLength(15)] public string SampleString { get; set; } } } \ No newline at end of file From fb21b736ee3bd3c5bb3ee6522b0b4bf2a37f403b Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Mon, 5 Jan 2015 12:36:13 -0800 Subject: [PATCH 041/118] Issue #1754 - Change List to IList in application model --- .../ApplicationModels/ActionModel.cs | 10 +++++----- .../ApplicationModels/ApplicationModel.cs | 4 ++-- .../ApplicationModels/ControllerModel.cs | 10 +++++----- .../DefaultActionModelBuilder.cs | 16 ++++++++++++---- .../DefaultControllerModelBuilder.cs | 18 ++++++++++++++---- .../ControllerActionDescriptorProvider.cs | 5 ++++- .../Logging/ActionModelValues.cs | 10 +++++----- ...ionConventionsApplicationModelConvention.cs | 5 ++++- .../DefaultActionModelBuilderTest.cs | 16 ++++++++-------- 9 files changed, 59 insertions(+), 35 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs index c1ea508f90..0829b27abc 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ActionModel.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels } } - public List ActionConstraints { get; private set; } + public IList ActionConstraints { get; private set; } public MethodInfo ActionMethod { get; } @@ -69,12 +69,12 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public ControllerModel Controller { get; set; } - public List Filters { get; private set; } + public IList Filters { get; private set; } - public List HttpMethods { get; private set; } + public IList HttpMethods { get; private set; } - public List Parameters { get; private set; } + public IList Parameters { get; private set; } - public List RouteConstraints { get; private set; } + public IList RouteConstraints { get; private set; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ApplicationModel.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ApplicationModel.cs index e102fbefca..cbb87b98d8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ApplicationModel.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ApplicationModel.cs @@ -13,8 +13,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Filters = new List(); } - public List Controllers { get; private set; } + public IList Controllers { get; private set; } - public List Filters { get; private set; } + public IList Filters { get; private set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ControllerModel.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ControllerModel.cs index 9b5ee6c2dd..ef22386b47 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ControllerModel.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ControllerModel.cs @@ -44,9 +44,9 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels other.AttributeRoutes.Select(a => new AttributeRouteModel(a))); } - public List ActionConstraints { get; private set; } + public IList ActionConstraints { get; private set; } - public List Actions { get; private set; } + public IList Actions { get; private set; } /// /// Gets or sets the for this controller. @@ -55,7 +55,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public ApplicationModel Application { get; set; } - public List AttributeRoutes { get; private set; } + public IList AttributeRoutes { get; private set; } public IReadOnlyList Attributes { get; } @@ -63,8 +63,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public TypeInfo ControllerType { get; private set; } - public List Filters { get; private set; } + public IList Filters { get; private set; } - public List RouteConstraints { get; private set; } + public IList RouteConstraints { get; private set; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs index 4bc8b26271..d9afde3509 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs @@ -254,8 +254,8 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels { var actionModel = new ActionModel(methodInfo, attributes); - actionModel.ActionConstraints.AddRange(attributes.OfType()); - actionModel.Filters.AddRange(attributes.OfType()); + AddRange(actionModel.ActionConstraints, attributes.OfType()); + AddRange(actionModel.Filters, attributes.OfType()); var actionName = attributes.OfType().FirstOrDefault(); if (actionName?.Name != null) @@ -280,13 +280,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels } var httpMethods = attributes.OfType(); - actionModel.HttpMethods.AddRange( + AddRange(actionModel.HttpMethods, httpMethods .Where(a => a.HttpMethods != null) .SelectMany(a => a.HttpMethods) .Distinct()); - actionModel.RouteConstraints.AddRange(attributes.OfType()); + AddRange(actionModel.RouteConstraints, attributes.OfType()); var routeTemplateProvider = attributes @@ -329,5 +329,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels routeTemplateProvider.Order == null && routeTemplateProvider.Name == null; } + + private static void AddRange(IList list, IEnumerable items) + { + foreach (var item in items) + { + list.Add(item); + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs index b8fa51f4d5..d4cb7cfed2 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.AspNet.Mvc.Description; @@ -120,11 +121,12 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels typeInfo.Name.Substring(0, typeInfo.Name.Length - "Controller".Length) : typeInfo.Name; - controllerModel.ActionConstraints.AddRange(attributes.OfType()); - controllerModel.Filters.AddRange(attributes.OfType()); - controllerModel.RouteConstraints.AddRange(attributes.OfType()); + AddRange(controllerModel.ActionConstraints, attributes.OfType()); + AddRange(controllerModel.Filters, attributes.OfType()); + AddRange(controllerModel.RouteConstraints, attributes.OfType()); - controllerModel.AttributeRoutes.AddRange( + AddRange( + controllerModel.AttributeRoutes, attributes.OfType().Select(rtp => new AttributeRouteModel(rtp))); var apiVisibility = attributes.OfType().FirstOrDefault(); @@ -141,5 +143,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels return controllerModel; } + + private static void AddRange(IList list, IEnumerable items) + { + foreach (var item in items) + { + list.Add(item); + } + } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs index c727fea474..347591e91b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs @@ -61,7 +61,10 @@ namespace Microsoft.AspNet.Mvc public ApplicationModel BuildModel() { var applicationModel = new ApplicationModel(); - applicationModel.Filters.AddRange(_globalFilters); + foreach (var filter in _globalFilters) + { + applicationModel.Filters.Add(filter); + } var assemblies = _assemblyProvider.CandidateAssemblies; var types = assemblies.SelectMany(a => a.DefinedTypes); diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs index 661a2cab65..f5f8d599aa 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionModelValues.cs @@ -55,19 +55,19 @@ namespace Microsoft.AspNet.Mvc.Logging /// The parameters of the action as . /// See . /// - public List Parameters { get; } + public IList Parameters { get; } /// /// The filters of the action as . /// See . /// - public List Filters { get; } + public IList Filters { get; } /// /// The route constraints on the controller as . /// See . /// - public List RouteConstraints { get; set; } + public IList RouteConstraints { get; set; } /// /// The attribute route model of the action as . @@ -78,13 +78,13 @@ namespace Microsoft.AspNet.Mvc.Logging /// /// The http methods this action supports. See . /// - public List HttpMethods { get; } + public IList HttpMethods { get; } /// /// The action constraints of the action as . /// See . /// - public List ActionConstraints { get; } + public IList ActionConstraints { get; } public override string Format() { diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs index bbf47089eb..0f7b36016a 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs @@ -57,7 +57,10 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim } } - controller.Actions.AddRange(newActions); + foreach (var action in newActions) + { + controller.Actions.Add(action); + } } private bool IsActionAttributeRouted(ActionModel action) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs index ee81a9b625..0223c7098b 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultActionModelBuilderTest.cs @@ -576,7 +576,7 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels // Assert var action = Assert.Single(actions); - Assert.Equal(new string[] { "GET" }, action.HttpMethods); + Assert.Equal(new string[] { "GET" }, action.HttpMethods); Assert.Equal("Products", action.AttributeRouteModel.Template); } @@ -595,10 +595,10 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Assert.Equal(2, actions.Count()); var action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "Products"); - Assert.Equal(new string[] { "GET", "POST" }, action.HttpMethods); + Assert.Equal(new string[] { "GET", "POST" }, action.HttpMethods); action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "v2/Products"); - Assert.Equal(new string[] { "GET", "POST" }, action.HttpMethods); + Assert.Equal(new string[] { "GET", "POST" }, action.HttpMethods); } [Fact] @@ -616,13 +616,13 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Assert.Equal(3, actions.Count()); var action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "Products"); - Assert.Equal(new string[] { "GET" }, action.HttpMethods); + Assert.Equal(new string[] { "GET" }, action.HttpMethods); action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "v2/Products"); - Assert.Equal(new string[] { "GET" }, action.HttpMethods); + Assert.Equal(new string[] { "GET" }, action.HttpMethods); action = Assert.Single(actions, a => a.AttributeRouteModel.Template == "Products/Buy"); - Assert.Equal(new string[] { "POST" }, action.HttpMethods); + Assert.Equal(new string[] { "POST" }, action.HttpMethods); } [Fact] @@ -640,10 +640,10 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Assert.Equal(2, actions.Count()); var action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == "Products"); - Assert.Equal(new string[] { "POST" }, action.HttpMethods); + Assert.Equal(new string[] { "POST" }, action.HttpMethods); action = Assert.Single(actions, a => a.AttributeRouteModel?.Template == null); - Assert.Equal(new string[] { "GET" }, action.HttpMethods); + Assert.Equal(new string[] { "GET" }, action.HttpMethods); } private class AccessibleActionModelBuilder : DefaultActionModelBuilder From 0a473b06003321f9dc9d455cf175c194c0a22cd6 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Mon, 5 Jan 2015 11:18:16 -0800 Subject: [PATCH 042/118] Remove ParameterModel.IsOptional The ParameterModel and ParameterDescriptor have had a notion of optionality for a while now, even though all parameters are treated as 'optional' in MVC. This change removes these settings. Optionality for overloading in webapi compat shim is reimplemented via a new binder metadata. --- .../DefaultActionModelBuilder.cs | 1 - .../ApplicationModels/ParameterModel.cs | 3 -- .../ControllerActionDescriptorBuilder.cs | 1 - .../DefaultApiDescriptionProvider.cs | 3 +- .../ParameterDescriptor.cs | 2 - ...erConventionsApplicationModelConvention.cs | 8 ++++ .../OverloadActionConstraint.cs | 10 +++- .../ParameterBinding/FromUriAttribute.cs | 3 ++ .../IOptionalBinderMetadata.cs | 17 +++++++ .../ApplicationModel/ParameterModelTest.cs | 1 - ...ControllerActionDescriptorProviderTests.cs | 36 --------------- .../DefaultApiDescriptionProviderTest.cs | 18 ++------ .../ApiExplorerTest.cs | 2 +- .../ApplicationModelTest.cs | 4 +- .../ApiControllerActionDiscoveryTest.cs | 46 ++++++++++++++++++- .../OverloadActionConstraintTest.cs | 9 ++-- .../Controllers/ParameterModelController.cs | 20 +++++--- 17 files changed, 107 insertions(+), 77 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs index d9afde3509..41c584a2b3 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs @@ -317,7 +317,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels parameterModel.BinderMetadata = attributes.OfType().FirstOrDefault(); parameterModel.ParameterName = parameterInfo.Name; - parameterModel.IsOptional = parameterInfo.HasDefaultValue; return parameterModel; } diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ParameterModel.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ParameterModel.cs index 49bef96ee1..2f5cae9481 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ParameterModel.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/ParameterModel.cs @@ -22,7 +22,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Action = other.Action; Attributes = new List(other.Attributes); BinderMetadata = other.BinderMetadata; - IsOptional = other.IsOptional; ParameterInfo = other.ParameterInfo; ParameterName = other.ParameterName; } @@ -33,8 +32,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels public IBinderMetadata BinderMetadata { get; set; } - public bool IsOptional { get; set; } - public ParameterInfo ParameterInfo { get; private set; } public string ParameterName { get; set; } diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs index 27c0536bff..730a2841f2 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs @@ -275,7 +275,6 @@ namespace Microsoft.AspNet.Mvc var parameterDescriptor = new ParameterDescriptor() { BinderMetadata = parameter.BinderMetadata, - IsOptional = parameter.IsOptional, Name = parameter.ParameterName, ParameterType = parameter.ParameterInfo.ParameterType, }; diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs index 830f96bbed..591737d166 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs @@ -263,7 +263,6 @@ namespace Microsoft.AspNet.Mvc.Description { var resourceParameter = new ApiParameterDescription { - IsOptional = parameter.IsOptional, Name = parameter.Name, ParameterDescriptor = parameter, Type = parameter.ParameterType, @@ -288,7 +287,7 @@ namespace Microsoft.AspNet.Mvc.Description var resourceParameter = new ApiParameterDescription { Source = ApiParameterSource.Path, - IsOptional = parameter.IsOptional && IsOptionalParameter(templateParameter), + IsOptional = IsOptionalParameter(templateParameter), Name = parameter.Name, ParameterDescriptor = parameter, Constraints = GetConstraints(_constraintResolver, templateParameter.InlineConstraints), diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs index 6a0ee3e646..8d69b0f948 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ParameterDescriptor.cs @@ -10,8 +10,6 @@ namespace Microsoft.AspNet.Mvc { public string Name { get; set; } - public bool IsOptional { get; set; } - public Type ParameterType { get; set; } public IBinderMetadata BinderMetadata { get; set; } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs index dc4456ced5..117c7a85d1 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Linq; using System.Web.Http; using Microsoft.AspNet.Mvc.ApplicationModels; @@ -46,6 +47,13 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim // Complex types are by-default from the body. parameter.BinderMetadata = new FromBodyAttribute(); } + + // If the parameter has a default value, we want to consider it as optional parameter by default. + var optionalMetadata = parameter.BinderMetadata as FromUriAttribute; + if (parameter.ParameterInfo.HasDefaultValue && optionalMetadata != null) + { + optionalMetadata.IsOptional = true; + } } } } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs index 1808d8cd99..d8efebb3bf 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/OverloadActionConstraint.cs @@ -93,9 +93,17 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim // We only consider parameters that are bound from the URL. if ((parameter.BinderMetadata is IRouteDataValueProviderMetadata || parameter.BinderMetadata is IQueryValueProviderMetadata) && - !parameter.IsOptional && ValueProviderResult.CanConvertFromString(parameter.ParameterType)) { + var optionalMetadata = parameter.BinderMetadata as IOptionalBinderMetadata; + if (optionalMetadata == null || optionalMetadata.IsOptional) + { + // Optional parameters are ignored in overloading. If a parameter doesn't specify that it's + // required then treat it as optional (MVC default). WebAPI parameters will all by-default + // specify themselves as required unless they have a default value. + continue; + } + var nameProvider = parameter.BinderMetadata as IModelNameProvider; var prefix = nameProvider?.Name ?? parameter.Name; diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs index 7e8533f522..9c78de7ce5 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs @@ -12,10 +12,13 @@ namespace System.Web.Http [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public class FromUriAttribute : Attribute, + IOptionalBinderMetadata, IQueryValueProviderMetadata, IRouteDataValueProviderMetadata, IModelNameProvider { + public bool IsOptional { get; set; } + /// public string Name { get; set; } } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.cs new file mode 100644 index 0000000000..d764f75a18 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.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. + +namespace Microsoft.AspNet.Mvc.ModelBinding +{ + /// + /// An that designates an optional parameter for the purposes + /// of WebAPI action overloading. Optional parameters do not participate in overloading, and + /// do not have to have a value for the action to be selected. + /// + /// This has no impact when used without WebAPI action overloading. + /// + public interface IOptionalBinderMetadata : IBinderMetadata + { + bool IsOptional { get; } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ParameterModelTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ParameterModelTest.cs index 3e8b43e0b6..cadec76d99 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ParameterModelTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/ParameterModelTest.cs @@ -19,7 +19,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels parameter.Action = new ActionModel(typeof(TestController).GetMethod("Edit"), new List()); parameter.BinderMetadata = (IBinderMetadata)parameter.Attributes[0]; - parameter.IsOptional = true; parameter.ParameterName = "id"; // Act diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs index e461393b17..168162a253 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs @@ -109,7 +109,6 @@ namespace Microsoft.AspNet.Mvc.Test var id = Assert.Single(main.Parameters); Assert.Equal("id", id.Name); - Assert.False(id.IsOptional); Assert.Null(id.BinderMetadata); Assert.Equal(typeof(int), id.ParameterType); } @@ -129,14 +128,12 @@ namespace Microsoft.AspNet.Mvc.Test var id = Assert.Single(main.Parameters, p => p.Name == "id"); Assert.Equal("id", id.Name); - Assert.False(id.IsOptional); Assert.Null(id.BinderMetadata); Assert.Equal(typeof(int), id.ParameterType); var entity = Assert.Single(main.Parameters, p => p.Name == "entity"); Assert.Equal("entity", entity.Name); - Assert.False(entity.IsOptional); Assert.IsType(entity.BinderMetadata); Assert.Equal(typeof(TestActionParameter), entity.ParameterType); } @@ -156,49 +153,22 @@ namespace Microsoft.AspNet.Mvc.Test var id = Assert.Single(main.Parameters, p => p.Name == "id"); Assert.Equal("id", id.Name); - Assert.False(id.IsOptional); Assert.Null(id.BinderMetadata); Assert.Equal(typeof(int), id.ParameterType); var upperCaseId = Assert.Single(main.Parameters, p => p.Name == "ID"); Assert.Equal("ID", upperCaseId.Name); - Assert.False(upperCaseId.IsOptional); Assert.Null(upperCaseId.BinderMetadata); Assert.Equal(typeof(int), upperCaseId.ParameterType); var pascalCaseId = Assert.Single(main.Parameters, p => p.Name == "Id"); Assert.Equal("Id", pascalCaseId.Name); - Assert.False(pascalCaseId.IsOptional); Assert.Null(id.BinderMetadata); Assert.Equal(typeof(int), pascalCaseId.ParameterType); } - [Theory] - [InlineData(nameof(ActionParametersController.OptionalInt), typeof(Nullable))] - [InlineData(nameof(ActionParametersController.OptionalChar), typeof(char))] - public void GetDescriptors_AddsParametersWithDefaultValues_AsOptionalParameters( - string actionName, - Type parameterType) - { - // Arrange & Act - var descriptors = GetDescriptors( - typeof(ActionParametersController).GetTypeInfo()); - - // Assert - var optional = Assert.Single(descriptors, - d => d.Name.Equals(actionName)); - - Assert.NotNull(optional.Parameters); - var id = Assert.Single(optional.Parameters); - - Assert.Equal("id", id.Name); - Assert.True(id.IsOptional); - Assert.Null(id.BinderMetadata); - Assert.Equal(parameterType, id.ParameterType); - } - [Fact] public void GetDescriptors_AddsParameters_DetectsFromBodyParameters() { @@ -216,7 +186,6 @@ namespace Microsoft.AspNet.Mvc.Test var entity = Assert.Single(fromBody.Parameters); Assert.Equal("entity", entity.Name); - Assert.False(entity.IsOptional); Assert.IsType(entity.BinderMetadata); Assert.Equal(typeof(TestActionParameter), entity.ParameterType); } @@ -238,7 +207,6 @@ namespace Microsoft.AspNet.Mvc.Test var entity = Assert.Single(notFromBody.Parameters); Assert.Equal("entity", entity.Name); - Assert.False(entity.IsOptional); Assert.Null(entity.BinderMetadata); Assert.Equal(typeof(TestActionParameter), entity.ParameterType); } @@ -1710,10 +1678,6 @@ namespace Microsoft.AspNet.Mvc.Test { public void RequiredInt(int id) { } - public void OptionalInt(int? id = 5) { } - - public void OptionalChar(char id = 'c') { } - public void FromBodyParameter([FromBody] TestActionParameter entity) { } public void NotFromBodyParameter(TestActionParameter entity) { } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs index 359add36fd..217e91c78d 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs @@ -121,7 +121,6 @@ namespace Microsoft.AspNet.Mvc.Description new ParameterDescriptor() { Name = "id", - IsOptional = true, ParameterType = typeof(int), }, new ParameterDescriptor() @@ -141,7 +140,7 @@ namespace Microsoft.AspNet.Mvc.Description var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "id"); Assert.NotNull(id.ModelMetadata); - Assert.True(id.IsOptional); + Assert.False(id.IsOptional); Assert.Same(action.Parameters[0], id.ParameterDescriptor); Assert.Equal(ApiParameterSource.Query, id.Source); Assert.Equal(typeof(int), id.Type); @@ -224,7 +223,6 @@ namespace Microsoft.AspNet.Mvc.Description var parameterDescriptor = new ParameterDescriptor { Name = "id", - IsOptional = true, ParameterType = typeof(int), }; action.Parameters = new List { parameterDescriptor }; @@ -280,7 +278,6 @@ namespace Microsoft.AspNet.Mvc.Description { BinderMetadata = new FromBodyAttribute(), Name = "id", - IsOptional = false, ParameterType = typeof(int), }; action.Parameters = new List { parameterDescriptor }; @@ -317,15 +314,11 @@ namespace Microsoft.AspNet.Mvc.Description } [Theory] - [InlineData("api/products/{id}", false, false)] - [InlineData("api/products/{id}", true, false)] - [InlineData("api/products/{id?}", false, false)] - [InlineData("api/products/{id?}", true, true)] - [InlineData("api/products/{id=5}", false, false)] - [InlineData("api/products/{id=5}", true, true)] - public void GetApiDescription_ParameterFromPathAndDescriptor_IsOptionalOnly_IfBothAreOptional( + [InlineData("api/products/{id}", false)] + [InlineData("api/products/{id?}", true)] + [InlineData("api/products/{id=5}", true)] + public void GetApiDescription_ParameterFromPathAndDescriptor_IsOptionalIfRouteParameterIsOptional( string template, - bool isDescriptorParameterOptional, bool expectedOptional) { // Arrange @@ -335,7 +328,6 @@ namespace Microsoft.AspNet.Mvc.Description var parameterDescriptor = new ParameterDescriptor { Name = "id", - IsOptional = isDescriptorParameterOptional, ParameterType = typeof(int), }; action.Parameters = new List { parameterDescriptor }; diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs index fb8ed2050f..59ae5c105d 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -324,7 +324,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("RangeRouteConstraint", Assert.Single(month.ConstraintTypes)); var day = Assert.Single(description.ParameterDescriptions, p => p.Name == "day"); - Assert.False(day.IsOptional); + Assert.True(day.IsOptional); Assert.Equal("Path", day.Source); Assert.Equal("IntRouteConstraint", Assert.Single(day.ConstraintTypes)); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApplicationModelTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApplicationModelTest.cs index 50cb6af45e..377020cf7a 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApplicationModelTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApplicationModelTest.cs @@ -57,13 +57,13 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var client = server.CreateClient(); // Act - var response = await client.GetAsync("http://localhost/ParameterModel/GetParameterIsOptional"); + var response = await client.GetAsync("http://localhost/ParameterModel/GetParameterMetadata"); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); var body = await response.Content.ReadAsStringAsync(); - Assert.Equal("True", body); + Assert.Equal("CoolMetadata", body); } } diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs index 4fd2013829..68c1fb0e95 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs @@ -277,7 +277,8 @@ namespace System.Web.Http foreach (var action in actions) { var parameter = Assert.Single(action.Parameters); - Assert.IsType(parameter.BinderMetadata); + var metadata = Assert.IsType(parameter.BinderMetadata); + Assert.False(metadata.IsOptional); } } @@ -335,6 +336,36 @@ namespace System.Web.Http } } + [Theory] + [InlineData(nameof(TestControllers.EventsController.GetWithId))] + [InlineData(nameof(TestControllers.EventsController.GetWithEmployee))] + public void GetActions_Parameters_ImplicitOptional(string name) + { + // Arrange + var provider = CreateProvider(); + + // Act + var context = new ActionDescriptorProviderContext(); + provider.Invoke(context); + + var results = context.Results.Cast(); + + // Assert + var controllerType = typeof(TestControllers.EventsController).GetTypeInfo(); + var actions = results + .Where(ad => ad.ControllerTypeInfo == controllerType) + .Where(ad => ad.Name == name) + .ToArray(); + + Assert.NotEmpty(actions); + foreach (var action in actions) + { + var parameter = Assert.Single(action.Parameters); + var metadata = Assert.IsType(parameter.BinderMetadata); + Assert.True(metadata.IsOptional); + } + } + private INestedProviderManager CreateProvider() { var assemblyProvider = new Mock(); @@ -455,5 +486,18 @@ namespace System.Web.Http.TestControllers public class Employee { } + + public class EventsController : ApiController + { + public IActionResult GetWithId(int id = 0) + { + return null; + } + + public IActionResult GetWithEmployee([FromUri] Employee e = null) + { + return null; + } + } } #endif \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs index 3be2ad1b66..3446efc6b2 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs @@ -165,9 +165,8 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BinderMetadata = new FromUriAttribute() { IsOptional = true }, Name = "quantity", - IsOptional = true, ParameterType = typeof(int), }, }; @@ -307,9 +306,8 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), + BinderMetadata = new FromUriAttribute() { IsOptional = true }, Name = "quantity", - IsOptional = true, ParameterType = typeof(int), }, }; @@ -430,8 +428,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim }, new ParameterDescriptor() { - BinderMetadata = new FromUriAttribute(), - IsOptional = true, + BinderMetadata = new FromUriAttribute() { IsOptional = true }, Name = "quantity", ParameterType = typeof(int), }, diff --git a/test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs b/test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs index 27a23e1ea1..4c579d3e36 100644 --- a/test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs +++ b/test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs @@ -4,25 +4,31 @@ using System; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.ApplicationModels; +using Microsoft.AspNet.Mvc.ModelBinding; namespace ApplicationModelWebSite { - // This controller uses an reflected model attribute to change a parameter to optional. + // This controller uses an reflected model attribute to change a parameter's binder metadata. + // + // This could be accomplished by simply making an attribute that implements IBinderMetadata, but + // this is part of a test for IParameterModelConvention. public class ParameterModelController : Controller { - public string GetParameterIsOptional([Optional] int? id) + public string GetParameterMetadata([Cool] int? id) { - var actionDescriptor = (ControllerActionDescriptor)ActionContext.ActionDescriptor; - - return actionDescriptor.Parameters[0].IsOptional.ToString(); + return ActionContext.ActionDescriptor.Parameters[0].BinderMetadata.GetType().Name; } - private class OptionalAttribute : Attribute, IParameterModelConvention + private class CoolAttribute : Attribute, IParameterModelConvention { public void Apply(ParameterModel model) { - model.IsOptional = true; + model.BinderMetadata = new CoolMetadata(); } } + + private class CoolMetadata : IBinderMetadata + { + } } } \ No newline at end of file From 227f564098aedfcaa8e7eafd453b518b089e1e3d Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Fri, 2 Jan 2015 13:52:04 -0800 Subject: [PATCH 043/118] Logging functional tests using ElmLogger --- Mvc.sln | 15 ++ .../Logging/LoggingActionSelectionTest.cs | 101 +++++++++++ .../Logging/LoggingAssert.cs | 96 +++++++++++ .../Logging/LoggingExtensions.cs | 161 ++++++++++++++++++ .../Logging/LoggingStartupTest.cs | 124 ++++++++++++++ ...Microsoft.AspNet.Mvc.FunctionalTests.kproj | 9 +- .../TestHelper.cs | 2 +- .../project.json | 4 +- .../Controllers/HomeController.cs | 15 ++ .../ElmLogSerializerMiddleware.cs | 117 +++++++++++++ .../LoggingWebSite/LoggingWebSite.kproj | 25 +++ .../Models/ActivityContextDto.cs | 15 ++ .../LoggingWebSite/Models/LogInfoDto.cs | 24 +++ .../LoggingWebSite/Models/RequestInfoDto.cs | 31 ++++ .../LoggingWebSite/Models/ScopeNodeDto.cs | 18 ++ .../Properties/debugSettings.json | 3 + test/WebSites/LoggingWebSite/Startup.cs | 47 +++++ .../LoggingWebSite/Views/Home/Index.cshtml | 1 + .../Views/Shared/_Layout.cshtml | 30 ++++ test/WebSites/LoggingWebSite/project.json | 28 +++ .../WebSites/LoggingWebSite/wwwroot/readme.md | 1 + 21 files changed, 863 insertions(+), 4 deletions(-) create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingActionSelectionTest.cs create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingAssert.cs create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingExtensions.cs create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingStartupTest.cs create mode 100644 test/WebSites/LoggingWebSite/Controllers/HomeController.cs create mode 100644 test/WebSites/LoggingWebSite/ElmLogSerializerMiddleware.cs create mode 100644 test/WebSites/LoggingWebSite/LoggingWebSite.kproj create mode 100644 test/WebSites/LoggingWebSite/Models/ActivityContextDto.cs create mode 100644 test/WebSites/LoggingWebSite/Models/LogInfoDto.cs create mode 100644 test/WebSites/LoggingWebSite/Models/RequestInfoDto.cs create mode 100644 test/WebSites/LoggingWebSite/Models/ScopeNodeDto.cs create mode 100644 test/WebSites/LoggingWebSite/Properties/debugSettings.json create mode 100644 test/WebSites/LoggingWebSite/Startup.cs create mode 100644 test/WebSites/LoggingWebSite/Views/Home/Index.cshtml create mode 100644 test/WebSites/LoggingWebSite/Views/Shared/_Layout.cshtml create mode 100644 test/WebSites/LoggingWebSite/project.json create mode 100644 test/WebSites/LoggingWebSite/wwwroot/readme.md diff --git a/Mvc.sln b/Mvc.sln index a47da8f452..5c1f9b359a 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -114,6 +114,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ValueProvidersWebSite", "te 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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -622,6 +624,18 @@ Global {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 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -677,5 +691,6 @@ Global {A853B2BA-4449-4908-A416-5A3C027FC22B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {14F79E79-AE79-48FA-95DE-D794EF4EABB3} = {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} EndGlobalSection EndGlobal diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingActionSelectionTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingActionSelectionTest.cs new file mode 100644 index 0000000000..23140ecc3d --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingActionSelectionTest.cs @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if ASPNET50 // Since Json.net serialization fails in CoreCLR +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using LoggingWebSite; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Mvc.Logging; +using Microsoft.AspNet.TestHost; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class LoggingActionSelectionTest + { + private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(LoggingWebSite)); + private readonly Action _app = new LoggingWebSite.Startup().Configure; + + [Fact] + public async Task Successful_ActionSelection_Logged() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var requestTraceId = Guid.NewGuid().ToString(); + + // Act + var response = await client.GetAsync(string.Format( + "http://localhost/home/index?{0}={1}", + LoggingExtensions.RequestTraceIdQueryKey, + requestTraceId)); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var responseData = await response.Content.ReadAsStringAsync(); + Assert.Equal("Home.Index", responseData); + + var logs = await GetLogsAsync(client, requestTraceId); + var scopeNode = logs.FindScope(nameof(MvcRouteHandler) + ".RouteAsync"); + + Assert.NotNull(scopeNode); + var logInfo = scopeNode.Messages.OfDataType() + .FirstOrDefault(); + + Assert.NotNull(logInfo); + Assert.NotNull(logInfo.State); + + dynamic actionSelection = logInfo.State; + Assert.True((bool)actionSelection.ActionSelected); + Assert.True((bool)actionSelection.ActionInvoked); + Assert.True((bool)actionSelection.Handled); + } + + [Fact] + public async Task Failed_ActionSelection_Logged() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + var requestTraceId = Guid.NewGuid().ToString(); + + // Act + var response = await client.GetAsync(string.Format( + "http://localhost/InvalidController/InvalidAction?{0}={1}", + LoggingExtensions.RequestTraceIdQueryKey, + requestTraceId)); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + + var logs = await GetLogsAsync(client, requestTraceId); + var scopeNode = logs.FindScope(nameof(MvcRouteHandler) + ".RouteAsync"); + + Assert.NotNull(scopeNode); + var logInfo = scopeNode.Messages.OfDataType() + .FirstOrDefault(); + Assert.NotNull(logInfo); + + dynamic actionSelection = logInfo.State; + Assert.False((bool)actionSelection.ActionSelected); + Assert.False((bool)actionSelection.ActionInvoked); + Assert.False((bool)actionSelection.Handled); + } + + private async Task> GetLogsAsync(HttpClient client, + string requestTraceId) + { + var responseData = await client.GetStringAsync("http://localhost/logs"); + var logActivities = JsonConvert.DeserializeObject>(responseData); + return logActivities.FilterByRequestTraceId(requestTraceId); + } + + } +} +#endif \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingAssert.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingAssert.cs new file mode 100644 index 0000000000..f218e507d6 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingAssert.cs @@ -0,0 +1,96 @@ +// 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 LoggingWebSite; +using Xunit.Sdk; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public static class LoggingAssert + { + /// + /// Compares two trees and verifies if the scope nodes are equal + /// + /// + /// + /// + public static bool ScopesEqual(ScopeNodeDto expected, ScopeNodeDto actual) + { + // To enable diagnosis, here a flat-list(pe-order traversal based) of + // these trees is provided. + if (!AreScopesEqual(expected, actual)) + { + var expectedScopes = new List(); + var actualScopes = new List(); + + TraverseScopeTree(expected, expectedScopes); + TraverseScopeTree(actual, actualScopes); + + throw new EqualException(expected: string.Join(", ", expectedScopes), + actual: string.Join(", ", actualScopes)); + } + + return true; + } + + /// + /// Compares two trees and verifies if the scope nodes are equal + /// + /// + /// + /// + private static bool AreScopesEqual(ScopeNodeDto root1, ScopeNodeDto root2) + { + if (root1 == null && root2 == null) + { + return true; + } + + if (root1 == null || root2 == null) + { + return false; + } + + if (!string.Equals(root1.State?.ToString(), root2.State?.ToString(), StringComparison.OrdinalIgnoreCase) + || root1.Children.Count != root2.Children.Count) + { + return false; + } + + bool isChildScopeEqual = true; + for (int i = 0; i < root1.Children.Count; i++) + { + isChildScopeEqual = AreScopesEqual(root1.Children[i], root2.Children[i]); + + if (!isChildScopeEqual) + { + break; + } + } + + return isChildScopeEqual; + } + + /// + /// Traverses the scope node sub-tree and collects the list scopes + /// + /// + /// + private static void TraverseScopeTree(ScopeNodeDto root, List scopes) + { + if (root == null) + { + return; + } + + scopes.Add(root.State?.ToString()); + + foreach (var childScope in root.Children) + { + TraverseScopeTree(childScope, scopes); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingExtensions.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingExtensions.cs new file mode 100644 index 0000000000..e647a38c60 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingExtensions.cs @@ -0,0 +1,161 @@ +// 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 LoggingWebSite; +using Microsoft.AspNet.WebUtilities; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public static class LoggingExtensions + { + public const string RequestTraceIdQueryKey = "RequestTraceId"; + + /// + /// Gets a scope node with the given name + /// + /// + /// + /// A scope node if found, else null + public static ScopeNodeDto FindScope(this IEnumerable activities, + string scopeName) + { + ScopeNodeDto node = null; + + foreach (var activity in activities) + { + if (activity.RepresentsScope) + { + node = GetScope(activity.Root, scopeName); + + // Ideally we do not expect multiple scopes with the same name + // to exist in the logs, so we break on the first found scope node. + // Note: The logs can contain multiple scopes with the same name across + // different requests, but the tests are expected to filter the logs by request + // (ex: using request trace id) and then find the scope by name. + if (node != null) + { + return node; + } + } + } + + return node; + } + + /// + /// Gets all the logs messages matching the given data type + /// + /// + /// + public static IEnumerable GetLogsByDataType(this IEnumerable activities) + { + var logInfos = new List(); + foreach (var activity in activities) + { + if (!activity.RepresentsScope) + { + var logInfo = activity.Root.Messages.OfDataType() + .FirstOrDefault(); + + if (logInfo != null) + { + logInfos.Add(logInfo); + } + } + else + { + GetLogsByDataType(activity.Root, logInfos); + } + } + + return logInfos; + } + + /// + /// Filters for logs activties created during application startup + /// + /// + /// + public static IEnumerable FilterByStartup(this IEnumerable activities) + { + return activities.Where(activity => activity.RequestInfo == null); + } + + /// + /// Filters log activities based on the given request. + /// + /// + /// The "RequestTraceId" query parameter value + /// + public static IEnumerable FilterByRequestTraceId(this IEnumerable activities, + string requestTraceId) + { + return activities.Where(activity => activity.RequestInfo != null + && string.Equals(GetQueryValue(activity.RequestInfo.Query, RequestTraceIdQueryKey), + requestTraceId, + StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Filters the log messages based on the given data type + /// + /// + /// + public static IEnumerable OfDataType(this IEnumerable logInfos) + { + return logInfos.Where(logInfo => logInfo.StateType != null + && logInfo.StateType.Equals(typeof(T))); + } + + /// + /// Traverses through the log node tree and gets the log messages whose StateType + /// matches the supplied data type. + /// + /// + /// + private static void GetLogsByDataType(ScopeNodeDto node, IList logInfoDtos) + { + foreach (var logInfo in node.Messages.OfDataType()) + { + logInfoDtos.Add(logInfo); + } + + foreach (var scopeNode in node.Children) + { + GetLogsByDataType(scopeNode, logInfoDtos); + } + } + + private static ScopeNodeDto GetScope(ScopeNodeDto root, string scopeName) + { + if (string.Equals(root.State?.ToString(), + scopeName, + StringComparison.OrdinalIgnoreCase)) + { + return root; + } + + foreach (var childNode in root.Children) + { + var foundNode = GetScope(childNode, scopeName); + + if (foundNode != null) + { + return foundNode; + } + } + + return null; + } + + private static string GetQueryValue(string query, string key) + { + var queryString = QueryHelpers.ParseQuery(query); + + return queryString[key]; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingStartupTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingStartupTest.cs new file mode 100644 index 0000000000..dbb285ee25 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingStartupTest.cs @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +#if ASPNET50 // Since Json.net serialization fails in CoreCLR +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using LoggingWebSite; +using LoggingWebSite.Controllers; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Mvc.Logging; +using Microsoft.AspNet.TestHost; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class LoggingStartupTest + { + private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(LoggingWebSite)); + private readonly Action _app = new LoggingWebSite.Startup().Configure; + + [Fact] + public async Task AssemblyValues_LoggedAtStartup() + { + // Arrange and Act + var logs = await GetLogsByDataTypeAsync(); + + // Assert + Assert.NotEmpty(logs); + foreach (var log in logs) + { + dynamic assembly = log.State; + Assert.NotNull(assembly); + Assert.Equal( + "LoggingWebSite, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null", + assembly.AssemblyName.ToString()); + } + } + + [Fact] + public async Task IsControllerValues_LoggedAtStartup() + { + // Arrange and Act + var logs = await GetLogsByDataTypeAsync(); + + // Assert + Assert.NotEmpty(logs); + foreach (var log in logs) + { + dynamic isController = log.State; + if (string.Equals(typeof(HomeController).AssemblyQualifiedName, isController.Type.ToString())) + { + Assert.Equal( + ControllerStatus.IsController, + Enum.Parse(typeof(ControllerStatus), isController.Status.ToString())); + } + else + { + Assert.NotEqual(ControllerStatus.IsController, + Enum.Parse(typeof(ControllerStatus), isController.Status.ToString())); + } + } + } + + [Fact] + public async Task ControllerModelValues_LoggedAtStartup() + { + // Arrange and Act + var logs = await GetLogsByDataTypeAsync(); + + // Assert + Assert.Single(logs); + dynamic controller = logs.First().State; + Assert.Equal("Home", controller.ControllerName.ToString()); + Assert.Equal(typeof(HomeController).AssemblyQualifiedName, controller.ControllerType.ToString()); + Assert.Equal("Index", controller.Actions[0].ActionName.ToString()); + Assert.Empty(controller.ApiExplorer.IsVisible); + Assert.Empty(controller.ApiExplorer.GroupName.ToString()); + Assert.Empty(controller.Attributes); + Assert.Empty(controller.Filters); + Assert.Empty(controller.ActionConstraints); + Assert.Empty(controller.RouteConstraints); + Assert.Empty(controller.AttributeRoutes); + } + + [Fact] + public async Task ActionDescriptorValues_LoggedAtStartup() + { + // Arrange and Act + var logs = await GetLogsByDataTypeAsync(); + + // Assert + Assert.Single(logs); + dynamic action = logs.First().State; + Assert.Equal("Index", action.Name.ToString()); + Assert.Empty(action.Parameters); + Assert.Empty(action.FilterDescriptors); + Assert.Equal("action", action.RouteConstraints[0].RouteKey.ToString()); + Assert.Equal("controller", action.RouteConstraints[1].RouteKey.ToString()); + Assert.Empty(action.RouteValueDefaults); + Assert.Empty(action.ActionConstraints.ToString()); + Assert.Empty(action.HttpMethods.ToString()); + Assert.Empty(action.Properties); + Assert.Equal("Home", action.ControllerName.ToString()); + } + + private async Task> GetLogsByDataTypeAsync() + { + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + var response = await client.GetStringAsync("http://localhost/logs"); + + var activityDtos = JsonConvert.DeserializeObject>(response); + + var logs = activityDtos.FilterByStartup().GetLogsByDataType(); + + return logs; + } + } +} +#endif \ No newline at end of file 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 93c5f46e8a..247e087053 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/Microsoft.AspNet.Mvc.FunctionalTests.kproj +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Microsoft.AspNet.Mvc.FunctionalTests.kproj @@ -1,4 +1,4 @@ - + 14.0 @@ -14,4 +14,9 @@ 2.0 - + + + + + + \ 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 d0e72ba54e..3086318a46 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/TestHelper.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/TestHelper.cs @@ -60,7 +60,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests services.AddInstance( typeof(ILoggerFactory), - NullLoggerFactory.Instance); + new LoggerFactory()); if (newServices != null) { diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json index 13f7590d1c..6b889106c6 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json @@ -16,6 +16,7 @@ "FiltersWebSite": "1.0.0", "FormatterWebSite": "1.0.0", "InlineConstraintsWebSite": "1.0.0", + "LoggingWebSite": "1.0.0", "ModelBindingWebSite": "1.0.0", "MvcSample.Web": "1.0.0", "PrecompilationWebSite": "1.0.0", @@ -35,7 +36,8 @@ "Microsoft.AspNet.TestHost": "1.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*", - "xunit.runner.kre": "1.0.0-*" + "xunit.runner.kre": "1.0.0-*", + "Microsoft.AspNet.WebUtilities": "1.0.0-*" }, "commands": { "test": "xunit.runner.kre" diff --git a/test/WebSites/LoggingWebSite/Controllers/HomeController.cs b/test/WebSites/LoggingWebSite/Controllers/HomeController.cs new file mode 100644 index 0000000000..d27ef6ec79 --- /dev/null +++ b/test/WebSites/LoggingWebSite/Controllers/HomeController.cs @@ -0,0 +1,15 @@ +// 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 LoggingWebSite.Controllers +{ + public class HomeController : Controller + { + public IActionResult Index() + { + return View(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/LoggingWebSite/ElmLogSerializerMiddleware.cs b/test/WebSites/LoggingWebSite/ElmLogSerializerMiddleware.cs new file mode 100644 index 0000000000..8225fe8daa --- /dev/null +++ b/test/WebSites/LoggingWebSite/ElmLogSerializerMiddleware.cs @@ -0,0 +1,117 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Diagnostics.Elm; +using Microsoft.AspNet.Http; +using Newtonsoft.Json; + +namespace LoggingWebSite +{ + public class ElmLogSerializerMiddleware + { + private readonly RequestDelegate _next; + + public ElmLogSerializerMiddleware(RequestDelegate next) + { + _next = next; + } + + public Task Invoke(HttpContext context, ElmStore elmStore) + { + var currentRequest = context.Request; + + var logActivities = GetLogDetails(elmStore); + + context.Response.StatusCode = 200; + context.Response.ContentType = "application/json"; + + var serializer = JsonSerializer.Create(); + using (var writer = new JsonTextWriter(new StreamWriter(stream: context.Response.Body, + encoding: Encoding.UTF8, + bufferSize: 1024, + leaveOpen: true))) + { + serializer.Serialize(writer, logActivities); + } + + return Task.FromResult(true); + } + + private IEnumerable GetLogDetails(ElmStore elmStore) + { + var activities = new List(); + foreach (var activity in elmStore.GetActivities().Reverse()) + { + var rootScopeNodeDto = new ScopeNodeDto(); + CopyScopeNodeTree(activity.Root, rootScopeNodeDto); + + activities.Add(new ActivityContextDto() + { + RequestInfo = GetRequestInfoDto(activity.HttpInfo), + Id = activity.Id, + RepresentsScope = activity.RepresentsScope, + Root = rootScopeNodeDto + }); + } + + return activities; + } + + private RequestInfoDto GetRequestInfoDto(HttpInfo httpInfo) + { + if (httpInfo == null) return null; + + return new RequestInfoDto() + { + ContentType = httpInfo.ContentType, + Cookies = httpInfo.Cookies.ToArray(), + Headers = httpInfo.Headers.ToArray(), + Query = httpInfo.Query.Value, + Host = httpInfo.Host.Value, + Method = httpInfo.Method, + Path = httpInfo.Path.Value, + Protocol = httpInfo.Protocol, + RequestID = httpInfo.RequestID, + Scheme = httpInfo.Scheme, + StatusCode = httpInfo.StatusCode + }; + } + + private LogInfoDto GetLogInfoDto(LogInfo logInfo) + { + return new LogInfoDto() + { + EventID = logInfo.EventID, + Exception = logInfo.Exception, + LoggerName = logInfo.Name, + LogLevel = logInfo.Severity, + State = logInfo.State, + StateType = logInfo.State?.GetType() + }; + } + + private void CopyScopeNodeTree(ScopeNode root, ScopeNodeDto rootDto) + { + rootDto.LoggerName = root.Name; + rootDto.State = root.State; + rootDto.StateType = root.State?.GetType(); + + foreach (var logInfo in root.Messages) + { + rootDto.Messages.Add(GetLogInfoDto(logInfo)); + } + + foreach (var scopeNode in root.Children) + { + ScopeNodeDto childDto = new ScopeNodeDto(); + + CopyScopeNodeTree(scopeNode, childDto); + + rootDto.Children.Add(childDto); + } + } + } +} \ No newline at end of file diff --git a/test/WebSites/LoggingWebSite/LoggingWebSite.kproj b/test/WebSites/LoggingWebSite/LoggingWebSite.kproj new file mode 100644 index 0000000000..a513d0edcc --- /dev/null +++ b/test/WebSites/LoggingWebSite/LoggingWebSite.kproj @@ -0,0 +1,25 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 0ad78ab5-d67c-49bc-81b1-0c51bfa82b5e + + + + + + + 2.0 + 40912 + + + + + + + + \ No newline at end of file diff --git a/test/WebSites/LoggingWebSite/Models/ActivityContextDto.cs b/test/WebSites/LoggingWebSite/Models/ActivityContextDto.cs new file mode 100644 index 0000000000..9f8483bf07 --- /dev/null +++ b/test/WebSites/LoggingWebSite/Models/ActivityContextDto.cs @@ -0,0 +1,15 @@ +using System; + +namespace LoggingWebSite +{ + public class ActivityContextDto + { + public Guid Id { get; set; } + + public RequestInfoDto RequestInfo { get; set; } + + public ScopeNodeDto Root { get; set; } + + public bool RepresentsScope { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/LoggingWebSite/Models/LogInfoDto.cs b/test/WebSites/LoggingWebSite/Models/LogInfoDto.cs new file mode 100644 index 0000000000..b70fe411bd --- /dev/null +++ b/test/WebSites/LoggingWebSite/Models/LogInfoDto.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.Framework.Logging; + +namespace LoggingWebSite +{ + public class LogInfoDto + { + public string LoggerName { get; set; } + + /// + /// Type of object representing the State. This is useful for tests + /// to filter the results + /// + public Type StateType { get; set; } + + public LogLevel LogLevel { get; set; } + + public int EventID { get; set; } + + public object State { get; set; } + + public Exception Exception { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/LoggingWebSite/Models/RequestInfoDto.cs b/test/WebSites/LoggingWebSite/Models/RequestInfoDto.cs new file mode 100644 index 0000000000..a347b23d9a --- /dev/null +++ b/test/WebSites/LoggingWebSite/Models/RequestInfoDto.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; +using Microsoft.AspNet.Http; + +namespace LoggingWebSite +{ + public class RequestInfoDto + { + public Guid RequestID { get; set; } + + public string Host { get; set; } + + public string Path { get; set; } + + public string ContentType { get; set; } + + public string Scheme { get; set; } + + public int StatusCode { get; set; } + + public string Method { get; set; } + + public string Protocol { get; set; } + + public IEnumerable> Headers { get; set; } + + public string Query { get; set; } + + public IEnumerable> Cookies { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/LoggingWebSite/Models/ScopeNodeDto.cs b/test/WebSites/LoggingWebSite/Models/ScopeNodeDto.cs new file mode 100644 index 0000000000..5acf5153be --- /dev/null +++ b/test/WebSites/LoggingWebSite/Models/ScopeNodeDto.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace LoggingWebSite +{ + public class ScopeNodeDto + { + public List Children { get; private set; } = new List(); + + public List Messages { get; private set; } = new List(); + + public object State { get; set; } + + public Type StateType { get; set; } + + public string LoggerName { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/LoggingWebSite/Properties/debugSettings.json b/test/WebSites/LoggingWebSite/Properties/debugSettings.json new file mode 100644 index 0000000000..a44fad34a3 --- /dev/null +++ b/test/WebSites/LoggingWebSite/Properties/debugSettings.json @@ -0,0 +1,3 @@ +{ + "Profiles": [] +} \ No newline at end of file diff --git a/test/WebSites/LoggingWebSite/Startup.cs b/test/WebSites/LoggingWebSite/Startup.cs new file mode 100644 index 0000000000..dbb5560c59 --- /dev/null +++ b/test/WebSites/LoggingWebSite/Startup.cs @@ -0,0 +1,47 @@ +// 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.Builder; +using Microsoft.AspNet.Routing; +using Microsoft.Framework.DependencyInjection; + +namespace LoggingWebSite +{ + public class Startup + { + public void Configure(IApplicationBuilder app) + { + var configuration = app.GetTestConfiguration(); + + app.UseServices(services => + { + services.AddElm(options => + { + // We want to log for all log levels and loggers + options.Filter = (loggerName, logLevel) => true; + }); + + services.AddMvc(configuration); + }); + + app.Map("/logs", (appBuilder) => + { + appBuilder.UseMiddleware(); + }); + + app.UseElmCapture(); + + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller}/{action}/{id?}", + defaults: new { controller = "Home", action = "Index" }); + + routes.MapRoute( + name: "api", + template: "{controller}/{id?}"); + }); + } + } +} diff --git a/test/WebSites/LoggingWebSite/Views/Home/Index.cshtml b/test/WebSites/LoggingWebSite/Views/Home/Index.cshtml new file mode 100644 index 0000000000..98b6900f87 --- /dev/null +++ b/test/WebSites/LoggingWebSite/Views/Home/Index.cshtml @@ -0,0 +1 @@ +Home.Index \ No newline at end of file diff --git a/test/WebSites/LoggingWebSite/Views/Shared/_Layout.cshtml b/test/WebSites/LoggingWebSite/Views/Shared/_Layout.cshtml new file mode 100644 index 0000000000..4311c0e375 --- /dev/null +++ b/test/WebSites/LoggingWebSite/Views/Shared/_Layout.cshtml @@ -0,0 +1,30 @@ + + + + + + @ViewBag.Title - My ASP.NET Application + + + +
+
+
+ @Html.ActionLink("LoggingWebApplication", "Index", "Home", new { area = "" }) +
+
+
    +
  • @Html.ActionLink("Home", "Index", "Home")
  • +
+
+
+
+
+ @RenderBody() +
+
+

© 2014 - My ASP.NET Application

+
+
+ + \ No newline at end of file diff --git a/test/WebSites/LoggingWebSite/project.json b/test/WebSites/LoggingWebSite/project.json new file mode 100644 index 0000000000..5e14c7f6e7 --- /dev/null +++ b/test/WebSites/LoggingWebSite/project.json @@ -0,0 +1,28 @@ +{ + "webroot": "wwwroot", + "version": "1.0.0-*", + "exclude": [ + "wwwroot" + ], + "packExclude": [ + "**.kproj", + "**.user", + "**.vspscc" + ], + "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" + }, + "dependencies": { + "Kestrel": "1.0.0-*", + "Microsoft.AspNet.Mvc": "6.0.0-*", + "Microsoft.AspNet.Server.IIS": "1.0.0-*", + "Microsoft.AspNet.Server.WebListener": "1.0.0-*", + "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "Microsoft.AspNet.Diagnostics.Elm": "1.0.0-*" + }, + "frameworks": { + "aspnet50": { }, + "aspnetcore50": { } + } +} diff --git a/test/WebSites/LoggingWebSite/wwwroot/readme.md b/test/WebSites/LoggingWebSite/wwwroot/readme.md new file mode 100644 index 0000000000..a60637ec43 --- /dev/null +++ b/test/WebSites/LoggingWebSite/wwwroot/readme.md @@ -0,0 +1 @@ +This file exists as Git does not allow to add empty directories into the repo \ No newline at end of file From 6df288bce7dc94c6afbeecc9260831bcea1c638d Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Tue, 6 Jan 2015 16:11:32 -0800 Subject: [PATCH 044/118] Correct StyleCop violations - StyleCop working again (handles C# 6.0 additions) though only locally for me - disable some new rules: - ConstFieldNamesMustBeginWithUpperCaseLetter - InstanceReadonlyElementsMustAppearBeforeInstanceNonReadonlyElements - StaticReadonlyElementsMustAppearBeforeStaticNonReadonlyElements - StaticReadonlyFieldsMustBeginWithUpperCaseLetter - PrefixCallsCorrectly - correct remaining violations - lots of long lines for example - use more `var`; some manual updates since StyleCop doesn't check seemingly-unused blocks nit: remove new trailing whitespace (was paranoid about adding it w/ fixes) --- Settings.StyleCop | 27 ++- .../TypeExtensions.cs | 2 +- .../ActionResults/FilePathResult.cs | 4 +- .../ActionResults/FileResult.cs | 2 +- .../AntiForgery/AntiForgeryToken.cs | 1 - .../AntiForgery/AntiForgeryTokenStore.cs | 6 +- .../DefaultActionModelBuilder.cs | 1 - src/Microsoft.AspNet.Mvc.Core/Controller.cs | 33 ++-- .../ControllerActionDescriptorProvider.cs | 2 +- .../DefaultAssemblyProvider.cs | 4 +- .../DefaultControllerActionArgumentBinder.cs | 3 +- ...DefaultPropertyBindingPredicateProvider.cs | 6 +- .../DefaultApiDescriptionProvider.cs | 4 +- .../Filters/IAuthorizationFilter.cs | 1 - .../Filters/IExceptionFilter.cs | 1 - .../Formatters/IInputFormatterSelector.cs | 1 - .../HttpMethodAttribute.cs | 2 +- .../Logging/ActionConstraintValues.cs | 3 +- .../Logging/ActionDescriptorValues.cs | 3 +- .../Logging/AttributeRouteModelValues.cs | 2 +- .../Logging/ControllerModelValues.cs | 2 +- .../Logging/FilterValues.cs | 5 +- .../ModelBinders/BodyModelBinder.cs | 2 +- .../ParameterBinding/ModelBindingHelper.cs | 2 +- .../Rendering/Html/DefaultHtmlGenerator.cs | 7 +- .../Rendering/Html/HtmlHelper.cs | 1 - .../Rendering/Html/TagBuilder.cs | 4 +- .../Routing/AttributeRoute.cs | 2 +- src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs | 3 +- .../ContentViewComponentResult.cs | 4 +- src/Microsoft.AspNet.Mvc.Core/ViewContext.cs | 3 +- .../ViewDataDictionaryOfT.cs | 10 +- .../BindAttribute.cs | 3 +- .../Binders/CollectionModelBinder.cs | 4 +- .../Binders/ComplexModelDtoModelBinder.cs | 2 +- .../Binders/CompositeModelBinder.cs | 30 +-- .../Binders/GenericModelBinder.cs | 2 +- .../Binders/ICompositeModelBinder.cs | 1 - .../Binders/KeyValuePairModelBinder.cs | 6 +- .../Binders/MutableObjectModelBinder.cs | 2 +- .../Metadata/IModelMetadataProvider.cs | 5 +- .../ModelBindingContext.cs | 5 +- .../ClientModelValidationContext.cs | 1 - .../DataAnnotationsModelValidatorProvider.cs | 14 +- .../Validation/DefaultBodyModelValidator.cs | 4 +- .../ModelClientValidationRegexRule.cs | 1 - .../Validation/ModelValidationNode.cs | 3 +- .../ValueProviders/CompositeValueProvider.cs | 3 +- .../MetadataAwareValueProvider.cs | 4 +- .../ReadableStringCollectionValueProvider.cs | 2 +- .../CompilationOptionsProviderExtension.cs | 8 +- .../Compilation/CompilerCache.cs | 2 +- .../Compilation/CompilerCacheEntry.cs | 8 +- .../DefaultRazorFileSystemCache.cs | 2 +- .../Compilation/RoslynCompilationService.cs | 3 +- .../IBeforeCompileContext.cs | 19 ++ .../IViewLocationExpander.cs | 6 +- .../PreCompileViews/RazorErrorExtensions.cs | 13 +- .../RazorFileInfoCollectionGenerator.cs | 1 - .../Razor/PreCompileViews/RazorPreCompiler.cs | 16 +- src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs | 4 +- src/Microsoft.AspNet.Mvc.Razor/RazorView.cs | 8 +- .../FormTagHelper.cs | 2 +- .../ValidationSummaryTagHelper.cs | 1 - .../ApiController.cs | 3 +- .../ContentNegotiator/CollectionExtensions.cs | 101 ++++++---- .../DefaultContentNegotiator.cs | 187 +++++++++++------- .../ContentNegotiator/FormattingUtilities.cs | 11 +- .../ContentNegotiator/IContentNegotiator.cs | 23 ++- .../ContentNegotiator/MediaTypeConstants.cs | 21 +- .../MediaTypeFormatterMatch.cs | 16 +- .../MediaTypeHeaderValueExtensions.cs | 53 ++--- .../MediaTypeWithQualityHeaderComparer.cs | 31 +-- .../ParsedMediaTypeHeaderValue.cs | 30 ++- .../StringWithQualityHeaderValueComparer.cs | 16 +- .../FormDataCollectionExtensions.cs | 2 +- .../HttpError.cs | 16 +- .../HttpRequestMessageExtensions.cs | 143 +++++++++----- .../WebApiCompatShimOptionsSetup.cs | 5 +- .../IAfterCompileContext.cs | 17 ++ src/Microsoft.AspNet.Mvc/ICompileModule.cs | 13 ++ src/Microsoft.AspNet.Mvc/MvcServices.cs | 24 +-- .../RazorPreCompileModule.cs | 26 +-- .../ControllerTests.cs | 9 +- .../ViewViewComponentResultTest.cs | 2 +- .../ModelBindingTests.cs | 4 +- .../TestUtils/MediaTypeConstants.cs | 21 +- .../Controllers/FromAttributesController.cs | 2 +- .../ModelBindingWebSite/Models/Department.cs | 2 +- .../Models/MixedUser_FromBody.cs | 2 +- 90 files changed, 674 insertions(+), 444 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Razor/IBeforeCompileContext.cs create mode 100644 src/Microsoft.AspNet.Mvc/IAfterCompileContext.cs create mode 100644 src/Microsoft.AspNet.Mvc/ICompileModule.cs diff --git a/Settings.StyleCop b/Settings.StyleCop index bb722e022f..7b8130298c 100644 --- a/Settings.StyleCop +++ b/Settings.StyleCop @@ -12,6 +12,11 @@ + + + False + + False @@ -22,6 +27,11 @@ False + + + False + + @@ -274,6 +284,11 @@ False + + + False + + False @@ -384,11 +399,21 @@ False + + + False + + False + + + False + + @@ -399,7 +424,7 @@ False - + diff --git a/src/Microsoft.AspNet.Mvc.Common/TypeExtensions.cs b/src/Microsoft.AspNet.Mvc.Common/TypeExtensions.cs index 2a8daaef8f..062c7b0ef1 100644 --- a/src/Microsoft.AspNet.Mvc.Common/TypeExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.Common/TypeExtensions.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc return false; } - for (int idx = 0; idx < t1.Length; ++idx) + for (var idx = 0; idx < t1.Length; ++idx) { if (t1[idx] != t2[idx]) { diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs index 001f1e23e3..9eb2b9610f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs @@ -119,7 +119,6 @@ namespace Microsoft.AspNet.Mvc throw new FileNotFoundException(message, path); } - // Internal for unit testing purposes only /// /// Creates a normalized representation of the given . The default /// implementation doesn't support files with '\' in the file name and treats the '\' as @@ -128,6 +127,7 @@ namespace Microsoft.AspNet.Mvc /// /// The path to normalize. /// The normalized path. + // Internal for unit testing purposes only protected internal virtual string NormalizePath([NotNull] string path) { // Unix systems support '\' as part of the file name. So '\' is not @@ -153,13 +153,13 @@ namespace Microsoft.AspNet.Mvc return path.Replace('\\', '/'); } - // Internal for unit testing purposes only /// /// Determines if the provided path is absolute or relative. The default implementation considers /// paths starting with '/' to be relative. /// /// The path to examine. /// True if the path is absolute. + // Internal for unit testing purposes only protected internal virtual bool IsPathRooted([NotNull] string path) { // We consider paths to be rooted if they start with '<>:' and do diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileResult.cs index 1b7fba8f97..7039145eb7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileResult.cs @@ -84,7 +84,7 @@ namespace Microsoft.AspNet.Mvc { builder.Append('%'); - int i = b; + var i = b; AddHexDigitToStringBuilder(i >> 4, builder); AddHexDigitToStringBuilder(i % 16, builder); } diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs index 3affb8fac6..b16dc900bd 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryToken.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. - namespace Microsoft.AspNet.Mvc { internal sealed class AntiForgeryToken diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs index 0ab6401f88..d9576fe4dc 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs @@ -23,7 +23,8 @@ namespace Microsoft.AspNet.Mvc public AntiForgeryToken GetCookieToken(HttpContext httpContext) { - var contextAccessor = httpContext.RequestServices.GetRequiredService>(); + var contextAccessor = + httpContext.RequestServices.GetRequiredService>(); if (contextAccessor.Value != null) { return contextAccessor.Value.CookieToken; @@ -56,7 +57,8 @@ namespace Microsoft.AspNet.Mvc { // Add the cookie to the request based context. // This is useful if the cookie needs to be reloaded in the context of the same request. - var contextAccessor = httpContext.RequestServices.GetRequiredService>(); + var contextAccessor = + httpContext.RequestServices.GetRequiredService>(); Debug.Assert(contextAccessor.Value == null, "AntiForgeryContext should be set only once per request."); contextAccessor.SetValue(new AntiForgeryContext() { CookieToken = token }); diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs index 41c584a2b3..76af6f93f3 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultActionModelBuilder.cs @@ -233,7 +233,6 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels typeInfo.GetRuntimeInterfaceMap(typeof(IDisposable)).TargetMethods[0] == methodInfo); } - /// /// Creates an for the given . /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index 53bb774895..c5f8b85b62 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -67,9 +67,8 @@ namespace Microsoft.AspNet.Mvc { if (_viewEngine == null) { - _viewEngine = ActionContext?. - HttpContext?. - RequestServices.GetRequiredService(); + _viewEngine = + ActionContext?.HttpContext?.RequestServices.GetRequiredService(); } return _viewEngine; @@ -797,7 +796,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using values from the controller's current + /// Updates the specified instance using values from the controller's current /// . /// /// The type of the model object. @@ -811,7 +810,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using values from the controller's current + /// Updates the specified instance using values from the controller's current /// and a . /// /// The type of the model object. @@ -826,7 +825,7 @@ namespace Microsoft.AspNet.Mvc { if (BindingContextProvider == null) { - var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), + var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), GetType().FullName); throw new InvalidOperationException(message); } @@ -836,7 +835,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using the and a + /// Updates the specified instance using the and a /// . /// /// The type of the model object. @@ -853,7 +852,7 @@ namespace Microsoft.AspNet.Mvc { if (BindingContextProvider == null) { - var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), + var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), GetType().FullName); throw new InvalidOperationException(message); } @@ -870,14 +869,14 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using values from the controller's current + /// Updates the specified instance using values from the controller's current /// and a . /// /// The type of the model object. /// The model instance to update. /// The prefix to use when looking up values in the current . /// - /// (s) which represent top-level properties + /// (s) which represent top-level properties /// which need to be included for the current model. /// A that on completion returns true if the update is successful [NonAction] @@ -907,7 +906,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using values from the controller's current + /// Updates the specified instance using values from the controller's current /// and a . /// /// The type of the model object. @@ -925,7 +924,7 @@ namespace Microsoft.AspNet.Mvc { if (BindingContextProvider == null) { - var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), + var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), GetType().FullName); throw new InvalidOperationException(message); } @@ -943,7 +942,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using the and a + /// Updates the specified instance using the and a /// . /// /// The type of the model object. @@ -951,7 +950,7 @@ namespace Microsoft.AspNet.Mvc /// The prefix to use when looking up values in the /// /// The used for looking up values. - /// (s) which represent top-level properties + /// (s) which represent top-level properties /// which need to be included for the current model. /// A that on completion returns true if the update is successful [NonAction] @@ -964,7 +963,7 @@ namespace Microsoft.AspNet.Mvc { if (BindingContextProvider == null) { - var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), + var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), GetType().FullName); throw new InvalidOperationException(message); } @@ -982,7 +981,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using the and a + /// Updates the specified instance using the and a /// . /// /// The type of the model object. @@ -1002,7 +1001,7 @@ namespace Microsoft.AspNet.Mvc { if (BindingContextProvider == null) { - var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), + var message = Resources.FormatPropertyOfTypeCannotBeNull(nameof(BindingContextProvider), GetType().FullName); throw new InvalidOperationException(message); } diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs index 347591e91b..cbc7cf575d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorProvider.cs @@ -5,8 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNet.Mvc.ApplicationModels; -using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.Filters; +using Microsoft.AspNet.Mvc.Logging; using Microsoft.Framework.Logging; using Microsoft.Framework.OptionsModel; diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs index 1a42fccba1..aa7f43835e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultAssemblyProvider.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc public class DefaultAssemblyProvider : IAssemblyProvider { /// - /// 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. /// protected virtual HashSet ReferenceAssemblies { get; } = new HashSet(StringComparer.Ordinal) @@ -44,7 +44,7 @@ namespace Microsoft.AspNet.Mvc /// /// Returns a list of libraries that references the assemblies in . - /// 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. /// /// A set of . diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs index e1b3fcb77e..8dbf3e9cbb 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs @@ -87,7 +87,8 @@ namespace Microsoft.AspNet.Mvc foreach (var parameter in parameterMetadata) { var parameterType = parameter.ModelType; - var modelBindingContext = GetModelBindingContext(parameter, actionBindingContext, operationBindingContext); + var modelBindingContext = + GetModelBindingContext(parameter, actionBindingContext, operationBindingContext); if (await actionBindingContext.ModelBinder.BindModelAsync(modelBindingContext)) { arguments[parameter.PropertyName] = modelBindingContext.Model; diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultPropertyBindingPredicateProvider.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultPropertyBindingPredicateProvider.cs index 1ef3dd7b05..a986fd453b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultPropertyBindingPredicateProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultPropertyBindingPredicateProvider.cs @@ -17,7 +17,8 @@ namespace Microsoft.AspNet.Mvc public class DefaultPropertyBindingPredicateProvider : IPropertyBindingPredicateProvider where TModel : class { - private static readonly Func _defaultFilter = (context, propertyName) => true; + private static readonly Func _defaultFilter = + (context, propertyName) => true; /// /// The prefix which is used while generating the property filter. @@ -57,7 +58,8 @@ namespace Microsoft.AspNet.Mvc } } - private Func GetPredicateFromExpression(IEnumerable>> includeExpressions) + private Func GetPredicateFromExpression( + IEnumerable>> includeExpressions) { var expression = ModelBindingHelper.GetIncludePredicateExpression(Prefix, includeExpressions.ToArray()); return expression.Compile(); diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs index 591737d166..48b2f1016f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs @@ -45,7 +45,6 @@ namespace Microsoft.AspNet.Mvc.Description get { return DefaultOrder.DefaultFrameworkSortOrder; } } - /// public void Invoke(ApiDescriptionProviderContext context, Action callNext) { @@ -157,7 +156,8 @@ namespace Microsoft.AspNet.Mvc.Description // Process parameters that only appear on the path template if any. foreach (var templateParameter in templateParameters) { - var parameterDescription = GetParameter(parameterDescriptor: null, templateParameter: templateParameter); + var parameterDescription = + GetParameter(parameterDescriptor: null, templateParameter: templateParameter); apiDescription.ParameterDescriptions.Add(parameterDescription); } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/IAuthorizationFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/IAuthorizationFilter.cs index 9b1cbccd50..af929411b5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/IAuthorizationFilter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/IAuthorizationFilter.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. - namespace Microsoft.AspNet.Mvc { public interface IAuthorizationFilter : IFilter diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/IExceptionFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/IExceptionFilter.cs index 8d6bbf41e9..d9cc2a1234 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/IExceptionFilter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/IExceptionFilter.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. - namespace Microsoft.AspNet.Mvc { public interface IExceptionFilter : IFilter diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatterSelector.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatterSelector.cs index 81dc2095d2..982a6d2c0a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatterSelector.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/IInputFormatterSelector.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. - namespace Microsoft.AspNet.Mvc { public interface IInputFormatterSelector diff --git a/src/Microsoft.AspNet.Mvc.Core/HttpMethodAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/HttpMethodAttribute.cs index 64f5913186..429a536cda 100644 --- a/src/Microsoft.AspNet.Mvc.Core/HttpMethodAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/HttpMethodAttribute.cs @@ -13,8 +13,8 @@ namespace Microsoft.AspNet.Mvc [AttributeUsage(AttributeTargets.Method, AllowMultiple = true, Inherited = true)] public abstract class HttpMethodAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider { - private int? _order; private readonly IEnumerable _httpMethods; + private int? _order; /// /// Creates a new with the given diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ActionConstraintValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionConstraintValues.cs index 9b68e6729b..af798e4519 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/ActionConstraintValues.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionConstraintValues.cs @@ -32,7 +32,8 @@ namespace Microsoft.AspNet.Mvc.Logging public Type ActionConstraintMetadataType { get; } /// - /// The constraint order if this is an . See . + /// The constraint order if this is an . See + /// . /// public int Order { get; } diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ActionDescriptorValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionDescriptorValues.cs index 0e7c488ea2..8dc5de8420 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/ActionDescriptorValues.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ActionDescriptorValues.cs @@ -25,7 +25,8 @@ namespace Microsoft.AspNet.Mvc.Logging AttributeRouteInfo = new AttributeRouteInfoValues(inner.AttributeRouteInfo); RouteValueDefaults = inner.RouteValueDefaults.ToDictionary(i => i.Key, i => i.Value.ToString()); ActionConstraints = inner.ActionConstraints?.Select(a => new ActionConstraintValues(a))?.ToList(); - HttpMethods = inner.ActionConstraints?.OfType().SelectMany(c => c.HttpMethods).ToList(); + HttpMethods = + inner.ActionConstraints?.OfType().SelectMany(c => c.HttpMethods).ToList(); Properties = inner.Properties.ToDictionary(i => i.Key.ToString(), i => i.Value.GetType()); var controllerActionDescriptor = inner as ControllerActionDescriptor; if (controllerActionDescriptor != null) diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteModelValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteModelValues.cs index 7a5167b4f7..ba9b0e42d9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteModelValues.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/AttributeRouteModelValues.cs @@ -1,8 +1,8 @@ // 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; using Microsoft.AspNet.Mvc.ApplicationModels; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc.Logging { diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs index 3ca2d23014..3d8c909b0e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/ControllerModelValues.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.Framework.Logging; using Microsoft.AspNet.Mvc.ApplicationModels; +using Microsoft.Framework.Logging; namespace Microsoft.AspNet.Mvc.Logging { diff --git a/src/Microsoft.AspNet.Mvc.Core/Logging/FilterValues.cs b/src/Microsoft.AspNet.Mvc.Core/Logging/FilterValues.cs index 60151950cb..2e6fa23978 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Logging/FilterValues.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Logging/FilterValues.cs @@ -1,4 +1,7 @@ -using System; +// 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.Framework.Logging; diff --git a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs index f8e89ecee8..61f29da8d9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ModelBinders/BodyModelBinder.cs @@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Mvc if (formatter == null) { var unsupportedContentType = Resources.FormatUnsupportedContentType( - bindingContext.OperationBindingContext.HttpContext.Request.ContentType); + bindingContext.OperationBindingContext.HttpContext.Request.ContentType); bindingContext.ModelState.AddModelError(bindingContext.ModelName, unsupportedContentType); // Should always return true so that the model binding process ends here. diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs index ae1965a9ca..19080e1c64 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs @@ -90,7 +90,7 @@ namespace Microsoft.AspNet.Mvc where TModel : class { var includeExpression = GetIncludePredicateExpression(prefix, includeExpressions); - Func predicate = includeExpression.Compile(); + var predicate = includeExpression.Compile(); return TryUpdateModelAsync( model, diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs index 0c3f2754d1..b36898f2ee 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs @@ -432,7 +432,8 @@ namespace Microsoft.AspNet.Mvc.Rendering if (defaultValue != null) { - selectList = UpdateSelectListItemsWithDefaultValue(selectList, defaultValue, allowMultiple, out selectedValues); + selectList = + UpdateSelectListItemsWithDefaultValue(selectList, defaultValue, allowMultiple, out selectedValues); } else { @@ -606,7 +607,9 @@ namespace Microsoft.AspNet.Mvc.Rendering // Only the style of the span is changed according to the errors if message is null or empty. // Otherwise the content and style is handled by the client-side validation. - var className = (modelError != null) ? HtmlHelper.ValidationMessageCssClassName : HtmlHelper.ValidationMessageValidCssClassName; + var className = (modelError != null) ? + HtmlHelper.ValidationMessageCssClassName : + HtmlHelper.ValidationMessageValidCssClassName; tagBuilder.AddCssClass(className); if (!string.IsNullOrEmpty(message)) diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs index 78f26f7633..f305184861 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/HtmlHelper.cs @@ -702,7 +702,6 @@ namespace Microsoft.AspNet.Mvc.Rendering return CreateForm(); } - protected virtual HtmlString GenerateHidden( ModelMetadata metadata, string name, diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TagBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TagBuilder.cs index 71272a2b2e..33ee0f6e4f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TagBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/TagBuilder.cs @@ -237,8 +237,8 @@ namespace Microsoft.AspNet.Mvc.Rendering case '-': case '_': case ':': - // Note '.' is valid according to the HTML 4.01 specification. Disallowed here to avoid confusion - // with CSS class selectors or when using jQuery. + // Note '.' is valid according to the HTML 4.01 specification. Disallowed here to avoid + // confusion with CSS class selectors or when using jQuery. return true; default: diff --git a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs index 362d4c67f1..7ed95e37b1 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Routing/AttributeRoute.cs @@ -20,12 +20,12 @@ namespace Microsoft.AspNet.Mvc.Routing public class AttributeRoute : IRouter { private readonly IRouter _next; + private readonly LinkGenerationDecisionTree _linkGenerationTree; private readonly TemplateRoute[] _matchingRoutes; private readonly IDictionary _namedEntries; private ILogger _logger; private ILogger _constraintLogger; - private readonly LinkGenerationDecisionTree _linkGenerationTree; /// /// Creates a new . diff --git a/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs b/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs index 66003c1a09..8dca748c38 100644 --- a/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/UrlHelper.cs @@ -22,7 +22,8 @@ namespace Microsoft.AspNet.Mvc private readonly IActionSelector _actionSelector; /// - /// Initializes a new instance of the class using the specified action context and action selector. + /// Initializes a new instance of the class using the specified action context and + /// action selector. /// /// The to access the action context /// of the current request. diff --git a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ContentViewComponentResult.cs b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ContentViewComponentResult.cs index 4ef89e3ce8..69dcc7fe12 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ContentViewComponentResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ViewComponents/ContentViewComponentResult.cs @@ -13,10 +13,10 @@ namespace Microsoft.AspNet.Mvc /// /// always writes HTML encoded text from the /// property. - /// + /// /// When using , the provided content will be HTML /// encoded and stored in . - /// + /// /// To write pre-encoded conent, use . /// public class ContentViewComponentResult : IViewComponentResult diff --git a/src/Microsoft.AspNet.Mvc.Core/ViewContext.cs b/src/Microsoft.AspNet.Mvc.Core/ViewContext.cs index f1e398c4ee..031fe6047b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ViewContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ViewContext.cs @@ -8,12 +8,11 @@ namespace Microsoft.AspNet.Mvc { public class ViewContext : ActionContext { - private DynamicViewData _viewBag; - // We need a default FormContext if the user uses html
instead of an MvcForm private readonly FormContext _defaultFormContext = new FormContext(); private FormContext _formContext; + private DynamicViewData _viewBag; public ViewContext( [NotNull] ActionContext actionContext, diff --git a/src/Microsoft.AspNet.Mvc.Core/ViewDataDictionaryOfT.cs b/src/Microsoft.AspNet.Mvc.Core/ViewDataDictionaryOfT.cs index a4c063f3dd..051d83b11c 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ViewDataDictionaryOfT.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ViewDataDictionaryOfT.cs @@ -7,7 +7,6 @@ namespace Microsoft.AspNet.Mvc { public class ViewDataDictionary : ViewDataDictionary { - // References may not show up due to ITypeActivator use in RazorPageActivator. /// /// Initializes a new instance of the class. /// @@ -15,6 +14,7 @@ namespace Microsoft.AspNet.Mvc /// For use when creating a for a new top-level scope. /// /// + // References may not show up due to ITypeActivator use in RazorPageActivator. public ViewDataDictionary( [NotNull] IModelMetadataProvider metadataProvider, [NotNull] ModelStateDictionary modelState) @@ -22,7 +22,6 @@ namespace Microsoft.AspNet.Mvc { } - // References may not show up due to ITypeActivator use in RazorPageActivator. /// /// Initializes a new instance of the class based in part on an /// existing instance. @@ -40,14 +39,12 @@ namespace Microsoft.AspNet.Mvc /// /// /// + // References may not show up due to ITypeActivator use in RazorPageActivator. public ViewDataDictionary([NotNull] ViewDataDictionary source) : base(source, declaredModelType: typeof(TModel)) { } - // Model parameter type is object to allow "model: null" calls even when TModel is a value type. A TModel - // parameter would likely require IEquatable type restrictions to pass expected null value to the base - // constructor. /// /// Initializes a new instance of the class based in part on an /// existing instance. This constructor is careful to avoid exceptions @@ -64,6 +61,9 @@ namespace Microsoft.AspNet.Mvc /// /// /// + // Model parameter type is object to allow "model: null" calls even when TModel is a value type. A TModel + // parameter would likely require IEquatable type restrictions to pass expected null value to the base + // constructor. public ViewDataDictionary([NotNull] ViewDataDictionary source, object model) : base(source, model, declaredModelType: typeof(TModel)) { diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs index 559d2a27a6..396d5a9b2a 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/BindAttribute.cs @@ -14,7 +14,8 @@ namespace Microsoft.AspNet.Mvc [AttributeUsage(AttributeTargets.Class | AttributeTargets.Parameter, AllowMultiple = false, Inherited = true)] public class BindAttribute : Attribute, IModelNameProvider, IPropertyBindingPredicateProvider { - private static readonly Func _defaultFilter = (context, propertyName) => true; + private static readonly Func _defaultFilter = + (context, propertyName) => true; private Func _predicateFromInclude; diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs index f64a0175e3..f2e6ff25d1 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CollectionModelBinder.cs @@ -47,7 +47,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var rawValueArray = RawValueToObjectArray(rawValue); foreach (var rawValueElement in rawValueArray) { - var innerModelMetadata = + var innerModelMetadata = bindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(null, typeof(TElement)); var innerBindingContext = new ModelBindingContext(bindingContext, bindingContext.ModelName, @@ -101,7 +101,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding foreach (var indexName in indexNames) { var fullChildName = ModelBindingHelper.CreateIndexModelName(bindingContext.ModelName, indexName); - var childModelMetadata = + var childModelMetadata = bindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(null, typeof(TElement)); var childBindingContext = new ModelBindingContext(bindingContext, fullChildName, childModelMetadata); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs index 8cfe74c323..3a133acbcb 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var propertyModelName = ModelBindingHelper.CreatePropertyModelName(bindingContext.ModelName, propertyMetadata.PropertyName); - var propertyBindingContext = new ModelBindingContext(bindingContext, + var propertyBindingContext = new ModelBindingContext(bindingContext, propertyModelName, propertyMetadata); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs index e94fd3a0b7..8d2ace61f4 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/CompositeModelBinder.cs @@ -80,16 +80,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding bindingContext.ModelName); } - var validationContext = new ModelValidationContext(bindingContext.OperationBindingContext.MetadataProvider, - bindingContext.OperationBindingContext.ValidatorProvider, - bindingContext.ModelState, - bindingContext.ModelMetadata, - containerMetadata: null); + var validationContext = new ModelValidationContext( + bindingContext.OperationBindingContext.MetadataProvider, + bindingContext.OperationBindingContext.ValidatorProvider, + bindingContext.ModelState, + bindingContext.ModelMetadata, + containerMetadata: null); newBindingContext.ValidationNode.Validate(validationContext, parentNode: null); } - bindingContext.OperationBindingContext.BodyBindingState = + bindingContext.OperationBindingContext.BodyBindingState = newBindingContext.OperationBindingContext.BodyBindingState; bindingContext.Model = newBindingContext.Model; return true; @@ -147,13 +148,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var metadata = oldBindingContext.ModelMetadata.BinderMetadata as IValueProviderMetadata; if (metadata != null) { - // ValueProvider property might contain a filtered list of value providers. + // ValueProvider property might contain a filtered list of value providers. // While deciding to bind a particular property which is annotated with a IValueProviderMetadata, - // instead of refiltering an already filtered list, we need to filter value providers from a global list - // of all value providers. This is so that every artifact that is explicitly marked using an - // IValueProviderMetadata can restrict model binding to only use value providers which support this + // instead of refiltering an already filtered list, we need to filter value providers from a global + // list of all value providers. This is so that every artifact that is explicitly marked using an + // IValueProviderMetadata can restrict model binding to only use value providers which support this // IValueProviderMetadata. - var valueProvider = oldBindingContext.OperationBindingContext.ValueProvider as IMetadataAwareValueProvider; + var valueProvider = + oldBindingContext.OperationBindingContext.ValueProvider as IMetadataAwareValueProvider; if (valueProvider != null) { newBindingContext.ValueProvider = valueProvider.Filter(metadata); @@ -171,7 +173,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var currentModelNeedsToReadBody = newIsFormatterBasedMetadataFound || newIsFormBasedMetadataFound; var oldState = oldBindingContext.OperationBindingContext.BodyBindingState; - // We need to throw if there are multiple models which can cause body to be read multiple times. + // We need to throw if there are multiple models which can cause body to be read multiple times. // Reading form data multiple times is ok since we cache form data. For the models marked to read using // formatters, multiple reads are not allowed. if (oldState == BodyBindingState.FormatterBased && currentModelNeedsToReadBody || @@ -179,7 +181,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { throw new InvalidOperationException(Resources.MultipleBodyParametersOrPropertiesAreNotAllowed); } - + var state = oldBindingContext.OperationBindingContext.BodyBindingState; if (newIsFormatterBasedMetadataFound) { @@ -187,7 +189,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } else if (newIsFormBasedMetadataFound && oldState != BodyBindingState.FormatterBased) { - // Only update the model binding state if we have not discovered formatter based state already. + // Only update the model binding state if we have not discovered formatter based state already. state = BodyBindingState.FormBased; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/GenericModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/GenericModelBinder.cs index 51a02c69b5..178addd3d0 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/GenericModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/GenericModelBinder.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var binder = (IModelBinder)_activator.CreateInstance(_serviceProvider, binderType); await binder.BindModelAsync(bindingContext); - // Was able to resolve a binder type, hence we should tell the model binding system to return + // Was able to resolve a binder type, hence we should tell the model binding system to return // true so that none of the other model binders participate. return true; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ICompositeModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ICompositeModelBinder.cs index 7ba9ad00c6..39e16fff9d 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ICompositeModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ICompositeModelBinder.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. - namespace Microsoft.AspNet.Mvc.ModelBinding { /// diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs index 431cb3ef22..e04a17d282 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/KeyValuePairModelBinder.cs @@ -28,12 +28,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding internal async Task> TryBindStrongModel(ModelBindingContext parentBindingContext, string propertyName) { - var propertyModelMetadata = + var propertyModelMetadata = parentBindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(modelAccessor: null, modelType: typeof(TModel)); - var propertyModelName = + var propertyModelName = ModelBindingHelper.CreatePropertyModelName(parentBindingContext.ModelName, propertyName); - var propertyBindingContext = + var propertyBindingContext = new ModelBindingContext(parentBindingContext, propertyModelName, propertyModelMetadata); if (await propertyBindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext)) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs index 359c50ff69..a23cfaf57c 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs @@ -220,7 +220,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var complexModelDtoMetadata = bindingContext.OperationBindingContext.MetadataProvider.GetMetadataForType(() => originalDto, typeof(ComplexModelDto)); - var dtoBindingContext = + var dtoBindingContext = new ModelBindingContext(bindingContext, bindingContext.ModelName, complexModelDtoMetadata); await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(dtoBindingContext); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs index 648e0a02b1..866b885a63 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/IModelMetadataProvider.cs @@ -11,7 +11,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { IEnumerable GetMetadataForProperties(object container, [NotNull] Type containerType); - ModelMetadata GetMetadataForProperty(Func modelAccessor, [NotNull] Type containerType, [NotNull] string propertyName); + ModelMetadata GetMetadataForProperty( + Func modelAccessor, + [NotNull] Type containerType, + [NotNull] string propertyName); ModelMetadata GetMetadataForType(Func modelAccessor, [NotNull] Type modelType); diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs index 58042ebcac..b8448eb8ef 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ModelBindingContext.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.AspNet.Http; namespace Microsoft.AspNet.Mvc.ModelBinding { @@ -13,7 +12,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// public class ModelBindingContext { - private static readonly Func + private static readonly Func _defaultPropertyFilter = (context, propertyName) => true; private string _modelName; @@ -53,7 +52,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } /// - /// Represents the associated with this context. + /// Represents the associated with this context. /// public OperationBindingContext OperationBindingContext { get; set; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ClientModelValidationContext.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ClientModelValidationContext.cs index 76101f10c7..05e47f2249 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ClientModelValidationContext.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ClientModelValidationContext.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. - namespace Microsoft.AspNet.Mvc.ModelBinding { public class ClientModelValidationContext diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs index 6bea81e5a5..d7d4e554a1 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DataAnnotationsModelValidatorProvider.cs @@ -22,13 +22,6 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // A factory for validators based on ValidationAttribute. internal delegate IModelValidator DataAnnotationsModelValidationFactory(ValidationAttribute attribute); - // A factory for validators based on IValidatableObject - private delegate IModelValidator DataAnnotationsValidatableObjectAdapterFactory(); - - private static bool _addImplicitRequiredAttributeForValueTypes = true; - private readonly Dictionary _attributeFactories = - BuildAttributeFactoriesDictionary(); - // Factories for validation attributes private static readonly DataAnnotationsModelValidationFactory _defaultAttributeFactory = (attribute) => new DataAnnotationsModelValidator(attribute); @@ -37,6 +30,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding private static readonly DataAnnotationsValidatableObjectAdapterFactory _defaultValidatableFactory = () => new ValidatableObjectAdapter(); + private static bool _addImplicitRequiredAttributeForValueTypes = true; + private readonly Dictionary _attributeFactories = + BuildAttributeFactoriesDictionary(); + + // A factory for validators based on IValidatableObject + private delegate IModelValidator DataAnnotationsValidatableObjectAdapterFactory(); + internal Dictionary AttributeFactories { get { return _attributeFactories; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs index ca3a9a7f0c..c42550e7d8 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/DefaultBodyModelValidator.cs @@ -56,7 +56,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // We don't need to recursively traverse the graph for types that shouldn't be validated var modelType = metadata.Model.GetType(); - if (IsTypeExcludedFromValidation(validationContext.ModelValidationContext.ExcludeFromValidationFilters, modelType)) + if (IsTypeExcludedFromValidation( + validationContext.ModelValidationContext.ExcludeFromValidationFilters, + modelType)) { return ShallowValidate(metadata, validationContext, validators); } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationRegexRule.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationRegexRule.cs index 8db5f92558..89067c81ad 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationRegexRule.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelClientValidationRegexRule.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. - namespace Microsoft.AspNet.Mvc.ModelBinding { public class ModelClientValidationRegexRule : ModelClientValidationRule diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationNode.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationNode.cs index c5a577149e..fdacf2d6d3 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationNode.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Validation/ModelValidationNode.cs @@ -104,7 +104,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { if (SuppressValidation || !validationContext.ModelState.CanAddErrors) { - // Short circuit if validation does not need to be applied or if we've reached the max number of validation errors. + // Short circuit if validation does not need to be applied or if we've reached the max number of + // validation errors. return; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs index 059d682bc0..d7654c7dd5 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/CompositeValueProvider.cs @@ -12,7 +12,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// /// Represents a whose values come from a collection of s. /// - public class CompositeValueProvider : Collection, IEnumerableValueProvider, IMetadataAwareValueProvider + public class CompositeValueProvider + : Collection, IEnumerableValueProvider, IMetadataAwareValueProvider { /// /// Initializes a new instance of . diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/MetadataAwareValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/MetadataAwareValueProvider.cs index 63123f688d..6d27eb6768 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/MetadataAwareValueProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/MetadataAwareValueProvider.cs @@ -9,7 +9,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// A value provider which can filter /// based on . /// - /// Represents a type implementing + /// + /// Represents a type implementing + /// public abstract class MetadataAwareValueProvider : IMetadataAwareValueProvider where TBinderMetadata : IValueProviderMetadata { diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs index 389a4ad00b..a6fbd97016 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/ReadableStringCollectionValueProvider.cs @@ -16,8 +16,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding where TBinderMetadata : IValueProviderMetadata { private readonly CultureInfo _culture; - private PrefixContainer _prefixContainer; private readonly Func> _valuesFactory; + private PrefixContainer _prefixContainer; private IReadableStringCollection _values; /// diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationOptionsProviderExtension.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationOptionsProviderExtension.cs index 4e0ccc6179..eca324b7be 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationOptionsProviderExtension.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationOptionsProviderExtension.cs @@ -15,8 +15,12 @@ namespace Microsoft.AspNet.Mvc.Razor /// Parses the for the current executing application and returns a /// used for Roslyn compilation. /// - /// A that reads compiler options. - /// The for the executing application. + /// + /// A that reads compiler options. + /// + /// + /// The for the executing application. + /// /// The for the current application. public static CompilationSettings GetCompilationSettings( [NotNull] this ICompilerOptionsProvider compilerOptionsProvider, diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs index 95d10908f5..c4a269d1bc 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// file system. /// public CompilerCache(IAssemblyProvider provider, IRazorFileSystemCache fileSystem) - : this (GetFileInfos(provider.CandidateAssemblies), fileSystem) + : this(GetFileInfos(provider.CandidateAssemblies), fileSystem) { } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs index ffbccc2d40..bd6f73102c 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCacheEntry.cs @@ -65,7 +65,13 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// Gets a flag that indicates if the file is precompiled. /// - public bool IsPreCompiled { get { return Hash != null; } } + public bool IsPreCompiled + { + get + { + return Hash != null; + } + } /// /// Gets or sets the for the nearest ViewStart that the compiled type diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileSystemCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileSystemCache.cs index b60d694078..d03d66def9 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileSystemCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileSystemCache.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Concurrent; using Microsoft.AspNet.FileSystems; -using Microsoft.Framework.OptionsModel; using Microsoft.Framework.Expiration.Interfaces; +using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.Razor { diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs index 047ce5acb3..43f236a1bc 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs @@ -119,8 +119,7 @@ namespace Microsoft.AspNet.Mvc.Razor } var type = assembly.GetExportedTypes() - .First(t => t.Name. - StartsWith(_classPrefix, StringComparison.Ordinal)); + .First(t => t.Name.StartsWith(_classPrefix, StringComparison.Ordinal)); return UncachedCompilationResult.Successful(type, compilationContent); } diff --git a/src/Microsoft.AspNet.Mvc.Razor/IBeforeCompileContext.cs b/src/Microsoft.AspNet.Mvc.Razor/IBeforeCompileContext.cs new file mode 100644 index 0000000000..b7a5979fd0 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/IBeforeCompileContext.cs @@ -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. + +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.Framework.Runtime +{ + [AssemblyNeutral] + public interface IBeforeCompileContext + { + CSharpCompilation CSharpCompilation { get; set; } + + IList Resources { get; } + + IList Diagnostics { get; } + } +} diff --git a/src/Microsoft.AspNet.Mvc.Razor/IViewLocationExpander.cs b/src/Microsoft.AspNet.Mvc.Razor/IViewLocationExpander.cs index 73c089dd35..df423fc89e 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/IViewLocationExpander.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/IViewLocationExpander.cs @@ -24,9 +24,9 @@ namespace Microsoft.AspNet.Mvc.Razor public interface IViewLocationExpander { /// - /// Invoked by a to determine the values that would be consumed by this instance of - /// . The calculated values are used to determine if the view location has - /// changed since the last time it was located. + /// Invoked by a to determine the values that would be consumed by this instance + /// of . The calculated values are used to determine if the view location + /// has changed since the last time it was located. /// /// The for the current view location /// expansion operation. diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorErrorExtensions.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorErrorExtensions.cs index 1c74e69617..8047d956c2 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorErrorExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorErrorExtensions.cs @@ -11,12 +11,13 @@ namespace Microsoft.AspNet.Mvc.Razor { public static Diagnostic ToDiagnostics([NotNull] this RazorError error, [NotNull] string filePath) { - var descriptor = new DiagnosticDescriptor(id: "Razor", - title: "Razor parsing error", - messageFormat: error.Message.Replace("{", "{{").Replace("}", "}}"), - category: "Razor.Parser", - defaultSeverity: DiagnosticSeverity.Error, - isEnabledByDefault: true); + var descriptor = new DiagnosticDescriptor( + id: "Razor", + title: "Razor parsing error", + messageFormat: error.Message.Replace("{", "{{").Replace("}", "}}"), + category: "Razor.Parser", + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true); var textSpan = new TextSpan(error.Location.AbsoluteIndex, error.Length); var linePositionStart = new LinePosition(error.Location.LineIndex, error.Location.CharacterIndex); diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs index 956aa62d63..5dc2b51c4a 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs @@ -43,7 +43,6 @@ namespace Microsoft.AspNet.Mvc.Razor return syntaxTree; } - protected virtual string GenerateFile([NotNull] RazorFileInfo fileInfo) { return string.Format(FileFormat, diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs index b41c3bf017..0b59bc46a2 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using Microsoft.AspNet.FileSystems; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.OptionsModel; @@ -79,7 +78,7 @@ namespace Microsoft.AspNet.Mvc.Razor private IEnumerable GetFileInfosRecursive(string currentPath) { - string path = currentPath; + var path = currentPath; var fileInfos = _fileSystem.GetDirectoryContents(path); if (!fileInfos.Exists) @@ -152,16 +151,3 @@ namespace Microsoft.AspNet.Mvc.Razor } } } - -namespace Microsoft.Framework.Runtime -{ - [AssemblyNeutral] - public interface IBeforeCompileContext - { - CSharpCompilation CSharpCompilation { get; set; } - - IList Resources { get; } - - IList Diagnostics { get; } - } -} diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index 2e63d1b060..9b5b48a719 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -122,12 +122,14 @@ namespace Microsoft.AspNet.Mvc.Razor { if (_tagHelperActivator == null) { - _tagHelperActivator = ViewContext.HttpContext.RequestServices.GetRequiredService(); + _tagHelperActivator = + ViewContext.HttpContext.RequestServices.GetRequiredService(); } return _tagHelperActivator; } } + /// /// Creates and activates a . /// diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs index 0133be5f7a..1ed944a9a7 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs @@ -33,8 +33,7 @@ namespace Microsoft.AspNet.Mvc.Razor IRazorPageActivator pageActivator, IViewStartProvider viewStartProvider, IRazorPage razorPage, - bool isPartial - ) + bool isPartial) { _viewEngine = viewEngine; _pageActivator = pageActivator; @@ -53,7 +52,6 @@ namespace Microsoft.AspNet.Mvc.Razor /// public bool IsPartial { get; } - private bool EnableInstrumentation { get { return _pageExecutionFeature != null; } @@ -97,8 +95,8 @@ namespace Microsoft.AspNet.Mvc.Razor bool executeViewStart) { var razorTextWriter = new RazorTextWriter(context.Writer, context.Writer.Encoding); - TextWriter writer = razorTextWriter; - IBufferedTextWriter bufferedWriter = razorTextWriter; + var writer = (TextWriter)razorTextWriter; + var bufferedWriter = (IBufferedTextWriter)razorTextWriter; if (EnableInstrumentation) { diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs index ca9246bf3d..ce6453de4a 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/FormTagHelper.cs @@ -65,7 +65,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// public override void Process(TagHelperContext context, TagHelperOutput output) { - bool antiForgeryDefault = true; + var antiForgeryDefault = true; var routePrefixedAttributes = output.FindPrefixedAttributes(RouteAttributePrefix); // If "action" is already set, it means the user is attempting to use a normal . diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs index 6a97dba832..3463b9acec 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs @@ -26,7 +26,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers [Activate] protected internal IHtmlGenerator Generator { get; set; } - // TODO: https://github.com/aspnet/Razor/issues/196 Change to ValidationSummary enum once #196 has been completed. /// /// If All or ModelOnly, appends a validation summary. Acceptable values are defined by the /// enum. diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs index 54ff8becf5..7c908794e7 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs @@ -41,7 +41,8 @@ namespace System.Web.Http } /// - /// Gets model state after the model binding process. This ModelState will be empty before model binding happens. + /// Gets model state after the model binding process. This ModelState will be empty before model binding + /// happens. /// public ModelStateDictionary ModelState { diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/CollectionExtensions.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/CollectionExtensions.cs index 6dd42fdce5..eb3b0ca83e 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/CollectionExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/CollectionExtensions.cs @@ -15,14 +15,15 @@ namespace System.Collections.Generic internal static class CollectionExtensions { /// - /// Return a new array with the value added to the end. Slow and best suited to long lived arrays with few writes relative to reads. + /// Return a new array with the value added to the end. Slow and best suited to long lived arrays with few + /// writes relative to reads. /// public static T[] AppendAndReallocate(this T[] array, T value) { Debug.Assert(array != null); - int originalLength = array.Length; - T[] newArray = new T[originalLength + 1]; + var originalLength = array.Length; + var newArray = new T[originalLength + 1]; array.CopyTo(newArray, 0); newArray[originalLength] = value; return newArray; @@ -36,7 +37,7 @@ namespace System.Collections.Generic { Debug.Assert(values != null); - T[] array = values as T[]; + var array = values as T[]; if (array == null) { array = values.ToArray(); @@ -52,13 +53,13 @@ namespace System.Collections.Generic { Debug.Assert(enumerable != null); - Collection collection = enumerable as Collection; + var collection = enumerable as Collection; if (collection != null) { return collection; } // Check for IList so that collection can wrap it instead of copying - IList list = enumerable as IList; + var list = enumerable as IList; if (list == null) { list = new List(enumerable); @@ -73,7 +74,7 @@ namespace System.Collections.Generic { Debug.Assert(enumerable != null); - IList list = enumerable as IList; + var list = enumerable as IList; if (list != null) { return list; @@ -82,8 +83,8 @@ namespace System.Collections.Generic } /// - /// Return the enumerable as a List of T, copying if required. Optimized for common case where it is an List of T - /// or a ListWrapperCollection of T. Avoid mutating the return value. + /// Return the enumerable as a List of T, copying if required. Optimized for common case where it is an List of + /// T or a ListWrapperCollection of T. Avoid mutating the return value. /// public static List AsList(this IEnumerable enumerable) { @@ -127,7 +128,7 @@ namespace System.Collections.Generic return default(T); case 1: - T value = list[0]; + var value = list[0]; return value; default: @@ -137,18 +138,21 @@ namespace System.Collections.Generic } /// - /// Returns a single value in list matching type TMatch if there is only one, null if there are none of type TMatch or calls the - /// errorAction with errorArg1 if there is more than one. + /// Returns a single value in list matching type TMatch if there is only one, null if there are none of type + /// TMatch or calls the errorAction with errorArg1 if there is more than one. /// - public static TMatch SingleOfTypeDefaultOrError(this IList list, Action errorAction, TArg1 errorArg1) where TMatch : class + public static TMatch SingleOfTypeDefaultOrError( + this IList list, + Action errorAction, + TArg1 errorArg1) where TMatch : class { Debug.Assert(list != null); Debug.Assert(errorAction != null); TMatch result = null; - for (int i = 0; i < list.Count; i++) + for (var i = 0; i < list.Count; i++) { - TMatch typedValue = list[i] as TMatch; + var typedValue = list[i] as TMatch; if (typedValue != null) { if (result == null) @@ -166,15 +170,16 @@ namespace System.Collections.Generic } /// - /// Convert an ICollection to an array, removing null values. Fast path for case where there are no null values. + /// Convert an ICollection to an array, removing null values. Fast path for case where there are no null + /// values. /// public static T[] ToArrayWithoutNulls(this ICollection collection) where T : class { Debug.Assert(collection != null); - T[] result = new T[collection.Count]; - int count = 0; - foreach (T value in collection) + var result = new T[collection.Count]; + var count = 0; + foreach (var value in collection) { if (value != null) { @@ -188,38 +193,46 @@ namespace System.Collections.Generic } else { - T[] trimmedResult = new T[count]; + var trimmedResult = new T[count]; Array.Copy(result, trimmedResult, count); return trimmedResult; } } /// - /// Convert the array to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for array input. + /// Convert the array to a Dictionary using the keySelector to extract keys from values and the specified + /// comparer. Optimized for array input. /// - public static Dictionary ToDictionaryFast(this TValue[] array, Func keySelector, IEqualityComparer comparer) + public static Dictionary ToDictionaryFast( + this TValue[] array, + Func keySelector, + IEqualityComparer comparer) { Debug.Assert(array != null); Debug.Assert(keySelector != null); - Dictionary dictionary = new Dictionary(array.Length, comparer); - for (int i = 0; i < array.Length; i++) + var dictionary = new Dictionary(array.Length, comparer); + for (var i = 0; i < array.Length; i++) { - TValue value = array[i]; + var value = array[i]; dictionary.Add(keySelector(value), value); } return dictionary; } /// - /// Convert the list to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for IList of T input with fast path for array. + /// Convert the list to a Dictionary using the keySelector to extract keys from values and the specified + /// comparer. Optimized for IList of T input with fast path for array. /// - public static Dictionary ToDictionaryFast(this IList list, Func keySelector, IEqualityComparer comparer) + public static Dictionary ToDictionaryFast( + this IList list, + Func keySelector, + IEqualityComparer comparer) { Debug.Assert(list != null); Debug.Assert(keySelector != null); - TValue[] array = list as TValue[]; + var array = list as TValue[]; if (array != null) { return ToDictionaryFast(array, keySelector, comparer); @@ -228,25 +241,29 @@ namespace System.Collections.Generic } /// - /// Convert the enumerable to a Dictionary using the keySelector to extract keys from values and the specified comparer. Fast paths for array and IList of T. + /// Convert the enumerable to a Dictionary using the keySelector to extract keys from values and the specified + /// comparer. Fast paths for array and IList of T. /// - public static Dictionary ToDictionaryFast(this IEnumerable enumerable, Func keySelector, IEqualityComparer comparer) + public static Dictionary ToDictionaryFast( + this IEnumerable enumerable, + Func keySelector, + IEqualityComparer comparer) { Debug.Assert(enumerable != null); Debug.Assert(keySelector != null); - TValue[] array = enumerable as TValue[]; + var array = enumerable as TValue[]; if (array != null) { return ToDictionaryFast(array, keySelector, comparer); } - IList list = enumerable as IList; + var list = enumerable as IList; if (list != null) { return ToDictionaryFastNoCheck(list, keySelector, comparer); } - Dictionary dictionary = new Dictionary(comparer); - foreach (TValue value in enumerable) + var dictionary = new Dictionary(comparer); + foreach (var value in enumerable) { dictionary.Add(keySelector(value), value); } @@ -254,18 +271,22 @@ namespace System.Collections.Generic } /// - /// Convert the list to a Dictionary using the keySelector to extract keys from values and the specified comparer. Optimized for IList of T input. No checking for other types. + /// Convert the list to a Dictionary using the keySelector to extract keys from values and the specified + /// comparer. Optimized for IList of T input. No checking for other types. /// - private static Dictionary ToDictionaryFastNoCheck(IList list, Func keySelector, IEqualityComparer comparer) + private static Dictionary ToDictionaryFastNoCheck( + IList list, + Func keySelector, + IEqualityComparer comparer) { Debug.Assert(list != null); Debug.Assert(keySelector != null); - int listCount = list.Count; - Dictionary dictionary = new Dictionary(listCount, comparer); - for (int i = 0; i < listCount; i++) + var listCount = list.Count; + var dictionary = new Dictionary(listCount, comparer); + for (var i = 0; i < listCount; i++) { - TValue value = list[i]; + var value = list[i]; dictionary.Add(keySelector(value), value); } return dictionary; diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/DefaultContentNegotiator.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/DefaultContentNegotiator.cs index fe95f0955e..4d411c78ff 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/DefaultContentNegotiator.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/DefaultContentNegotiator.cs @@ -9,9 +9,7 @@ using System.Diagnostics; using System.Linq; using System.Net.Http.Headers; using System.Text; -using System.Web.Http; using Microsoft.AspNet.Mvc; -using System.Net.Http.Formatting; namespace System.Net.Http.Formatting { @@ -48,35 +46,39 @@ namespace System.Net.Http.Formatting public bool ExcludeMatchOnTypeOnly { get; private set; } /// - /// Performs content negotiating by selecting the most appropriate out of the passed in - /// for the given that can serialize an object of the given - /// . + /// Performs content negotiating by selecting the most appropriate out of the + /// passed in for the given that can serialize an + /// object of the given . /// /// The type to be serialized. /// The request. /// The set of objects from which to choose. - /// The result of the negotiation containing the most appropriate instance, - /// or null if there is no appropriate formatter. - public virtual ContentNegotiationResult Negotiate([NotNull] Type type, [NotNull] HttpRequestMessage request, [NotNull] IEnumerable formatters) + /// The result of the negotiation containing the most appropriate + /// instance, or null if there is no appropriate formatter. + public virtual ContentNegotiationResult Negotiate( + [NotNull] Type type, + [NotNull] HttpRequestMessage request, + [NotNull] IEnumerable formatters) { // Go through each formatter to compute how well it matches. - Collection matches = ComputeFormatterMatches(type, request, formatters); + var matches = ComputeFormatterMatches(type, request, formatters); // Select best formatter match among the matches - MediaTypeFormatterMatch bestFormatterMatch = SelectResponseMediaTypeFormatter(matches); + var bestFormatterMatch = SelectResponseMediaTypeFormatter(matches); // We found a best formatter if (bestFormatterMatch != null) { // Find the best character encoding for the selected formatter - Encoding bestEncodingMatch = SelectResponseCharacterEncoding(request, bestFormatterMatch.Formatter); + var bestEncodingMatch = SelectResponseCharacterEncoding(request, bestFormatterMatch.Formatter); if (bestEncodingMatch != null) { bestFormatterMatch.MediaType.CharSet = bestEncodingMatch.WebName; } - MediaTypeHeaderValue bestMediaType = bestFormatterMatch.MediaType; - MediaTypeFormatter bestFormatter = bestFormatterMatch.Formatter.GetPerRequestFormatterInstance(type, request, bestMediaType); + var bestMediaType = bestFormatterMatch.MediaType; + var bestFormatter = + bestFormatterMatch.Formatter.GetPerRequestFormatterInstance(type, request, bestMediaType); return new ContentNegotiationResult(bestFormatter, bestMediaType); } @@ -84,25 +86,28 @@ namespace System.Net.Http.Formatting } /// - /// Determine how well each formatter matches by associating a value - /// with the formatter. Then associate the quality of the match based on q-factors and other parameters. The result of this - /// method is a collection of the matches found categorized and assigned a quality value. + /// Determine how well each formatter matches by associating a + /// value with the formatter. Then associate the quality of the match based on q-factors and other parameters. + /// The result of this method is a collection of the matches found categorized and assigned a quality value. /// /// The type to be serialized. /// The request. /// The set of objects from which to choose. /// A collection containing all the matches. - protected virtual Collection ComputeFormatterMatches([NotNull] Type type, [NotNull] HttpRequestMessage request, [NotNull] IEnumerable formatters) + protected virtual Collection ComputeFormatterMatches( + [NotNull] Type type, + [NotNull] HttpRequestMessage request, + [NotNull] IEnumerable formatters) { IEnumerable sortedAcceptValues = null; // Go through each formatter to find how well it matches. - ListWrapperCollection matches = new ListWrapperCollection(); - MediaTypeFormatter[] writingFormatters = GetWritingFormatters(formatters); - for (int i = 0; i < writingFormatters.Length; i++) + var matches = + new ListWrapperCollection(); + var writingFormatters = GetWritingFormatters(formatters); + for (var i = 0; i < writingFormatters.Length; i++) { - MediaTypeFormatter formatter = writingFormatters[i]; - MediaTypeFormatterMatch match = null; + var formatter = writingFormatters[i]; // Check first that formatter can write the actual type if (!formatter.CanWriteType(type)) @@ -117,7 +122,9 @@ namespace System.Net.Http.Formatting // Sort the Accept header values in descending order based on q-factor sortedAcceptValues = SortMediaTypeWithQualityHeaderValuesByQFactor(request.Headers.Accept); } - if ((match = MatchAcceptHeader(sortedAcceptValues, formatter)) != null) + + var match = MatchAcceptHeader(sortedAcceptValues, formatter); + if (match != null) { matches.Add(match); continue; @@ -132,7 +139,7 @@ namespace System.Net.Http.Formatting // Check whether we should match on type or stop the matching process. // The latter is used to generate 406 (Not Acceptable) status codes. - bool shouldMatchOnType = ShouldMatchOnType(sortedAcceptValues); + var shouldMatchOnType = ShouldMatchOnType(sortedAcceptValues); // Match against the type of object we are writing out if (shouldMatchOnType && (match = MatchType(type, formatter)) != null) @@ -150,11 +157,12 @@ namespace System.Net.Http.Formatting /// /// The collection of matches. /// The determined to be the best match. - protected virtual MediaTypeFormatterMatch SelectResponseMediaTypeFormatter([NotNull] ICollection matches) + protected virtual MediaTypeFormatterMatch SelectResponseMediaTypeFormatter( + [NotNull] ICollection matches) { // Performance-sensitive - List matchList = matches.AsList(); + var matchList = matches.AsList(); MediaTypeFormatterMatch bestMatchOnType = null; MediaTypeFormatterMatch bestMatchOnAcceptHeaderLiteral = null; @@ -164,9 +172,9 @@ namespace System.Net.Http.Formatting MediaTypeFormatterMatch bestMatchOnRequestMediaType = null; // Go through each formatter to find the best match in each category. - for (int i = 0; i < matchList.Count; i++) + for (var i = 0; i < matchList.Count; i++) { - MediaTypeFormatterMatch match = matchList[i]; + var match = matchList[i]; switch (match.Ranking) { case MediaTypeFormatterMatchRanking.MatchOnCanWriteType: @@ -186,13 +194,15 @@ namespace System.Net.Http.Formatting case MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderSubtypeMediaRange: // Matches on accept headers must choose the highest quality match. // A match of 0.0 means we won't use it at all. - bestMatchOnAcceptHeaderSubtypeMediaRange = UpdateBestMatch(bestMatchOnAcceptHeaderSubtypeMediaRange, match); + bestMatchOnAcceptHeaderSubtypeMediaRange = + UpdateBestMatch(bestMatchOnAcceptHeaderSubtypeMediaRange, match); break; case MediaTypeFormatterMatchRanking.MatchOnRequestAcceptHeaderAllMediaRange: // Matches on accept headers must choose the highest quality match. // A match of 0.0 means we won't use it at all. - bestMatchOnAcceptHeaderAllMediaRange = UpdateBestMatch(bestMatchOnAcceptHeaderAllMediaRange, match); + bestMatchOnAcceptHeaderAllMediaRange = + UpdateBestMatch(bestMatchOnAcceptHeaderAllMediaRange, match); break; case MediaTypeFormatterMatchRanking.MatchOnRequestMediaType: @@ -206,12 +216,13 @@ namespace System.Net.Http.Formatting } // If we received matches based on both supported media types and from media type mappings, - // we want to give precedence to the media type mappings, but only if their quality is >= that of the supported media type. - // We do this because media type mappings are the user's extensibility point and must take precedence over normal - // supported media types in the case of a tie. The 99% case is where both have quality 1.0. + // we want to give precedence to the media type mappings, but only if their quality is >= that of the + // supported media type. We do this because media type mappings are the user's extensibility point and must + // take precedence over normal supported media types in the case of a tie. The 99% case is where both have + // quality 1.0. if (bestMatchOnMediaTypeMapping != null) { - MediaTypeFormatterMatch mappingOverride = bestMatchOnMediaTypeMapping; + var mappingOverride = bestMatchOnMediaTypeMapping; mappingOverride = UpdateBestMatch(mappingOverride, bestMatchOnAcceptHeaderLiteral); mappingOverride = UpdateBestMatch(mappingOverride, bestMatchOnAcceptHeaderSubtypeMediaRange); mappingOverride = UpdateBestMatch(mappingOverride, bestMatchOnAcceptHeaderAllMediaRange); @@ -256,21 +267,24 @@ namespace System.Net.Http.Formatting /// If no encoding is found then we use the default for the formatter. /// /// The determined to be the best match. - protected virtual Encoding SelectResponseCharacterEncoding([NotNull] HttpRequestMessage request, [NotNull] MediaTypeFormatter formatter) + protected virtual Encoding SelectResponseCharacterEncoding( + [NotNull] HttpRequestMessage request, + [NotNull] MediaTypeFormatter formatter) { // If there are any SupportedEncodings then we pick an encoding - List supportedEncodings = formatter.SupportedEncodings.ToList(); + var supportedEncodings = formatter.SupportedEncodings.ToList(); if (supportedEncodings.Count > 0) { // Sort Accept-Charset header values - IEnumerable sortedAcceptCharsetValues = SortStringWithQualityHeaderValuesByQFactor(request.Headers.AcceptCharset); + var sortedAcceptCharsetValues = + SortStringWithQualityHeaderValuesByQFactor(request.Headers.AcceptCharset); // Check for match based on accept-charset headers foreach (StringWithQualityHeaderValue acceptCharset in sortedAcceptCharsetValues) { - for (int i = 0; i < supportedEncodings.Count; i++) + for (var i = 0; i < supportedEncodings.Count; i++) { - Encoding encoding = supportedEncodings[i]; + var encoding = supportedEncodings[i]; if (encoding != null && acceptCharset.Quality != FormattingUtilities.NoMatch && (acceptCharset.Value.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase) || acceptCharset.Value.Equals("*", StringComparison.Ordinal))) @@ -292,17 +306,22 @@ namespace System.Net.Http.Formatting /// /// The sorted accept header values to match. /// The formatter to match against. - /// A indicating the quality of the match or null is no match. - protected virtual MediaTypeFormatterMatch MatchAcceptHeader([NotNull] IEnumerable sortedAcceptValues, [NotNull] MediaTypeFormatter formatter) + /// + /// A indicating the quality of the match or null is no match. + /// + protected virtual MediaTypeFormatterMatch MatchAcceptHeader( + [NotNull] IEnumerable sortedAcceptValues, + [NotNull] MediaTypeFormatter formatter) { foreach (MediaTypeWithQualityHeaderValue acceptMediaTypeValue in sortedAcceptValues) { - List supportedMediaTypes = formatter.SupportedMediaTypes.ToList(); - for (int i = 0; i < supportedMediaTypes.Count; i++) + var supportedMediaTypes = formatter.SupportedMediaTypes.ToList(); + for (var i = 0; i < supportedMediaTypes.Count; i++) { - MediaTypeHeaderValue supportedMediaType = supportedMediaTypes[i]; + var supportedMediaType = supportedMediaTypes[i]; MediaTypeHeaderValueRange range; - if (supportedMediaType != null && acceptMediaTypeValue.Quality != FormattingUtilities.NoMatch && + if (supportedMediaType != null && + acceptMediaTypeValue.Quality != FormattingUtilities.NoMatch && supportedMediaType.IsSubsetOf(acceptMediaTypeValue, out range)) { MediaTypeFormatterMatchRanking ranking; @@ -321,7 +340,11 @@ namespace System.Net.Http.Formatting break; } - return new MediaTypeFormatterMatch(formatter, supportedMediaType, acceptMediaTypeValue.Quality, ranking); + return new MediaTypeFormatterMatch( + formatter, + supportedMediaType, + acceptMediaTypeValue.Quality, + ranking); } } } @@ -335,21 +358,29 @@ namespace System.Net.Http.Formatting /// /// The request to match. /// The formatter to match against. - /// A indicating the quality of the match or null is no match. - protected virtual MediaTypeFormatterMatch MatchRequestMediaType([NotNull] HttpRequestMessage request, [NotNull] MediaTypeFormatter formatter) + /// + /// A indicating the quality of the match or null is no match. + /// + protected virtual MediaTypeFormatterMatch MatchRequestMediaType( + [NotNull] HttpRequestMessage request, + [NotNull] MediaTypeFormatter formatter) { if (request.Content != null) { - MediaTypeHeaderValue requestMediaType = request.Content.Headers.ContentType; + var requestMediaType = request.Content.Headers.ContentType; if (requestMediaType != null) { - List supportedMediaTypes = formatter.SupportedMediaTypes.ToList(); - for (int i = 0; i < supportedMediaTypes.Count; i++) + var supportedMediaTypes = formatter.SupportedMediaTypes.ToList(); + for (var i = 0; i < supportedMediaTypes.Count; i++) { - MediaTypeHeaderValue supportedMediaType = supportedMediaTypes[i]; + var supportedMediaType = supportedMediaTypes[i]; if (supportedMediaType != null && supportedMediaType.IsSubsetOf(requestMediaType)) { - return new MediaTypeFormatterMatch(formatter, supportedMediaType, FormattingUtilities.Match, MediaTypeFormatterMatchRanking.MatchOnRequestMediaType); + return new MediaTypeFormatterMatch( + formatter, + supportedMediaType, + FormattingUtilities.Match, + MediaTypeFormatterMatchRanking.MatchOnRequestMediaType); } } } @@ -365,8 +396,11 @@ namespace System.Net.Http.Formatting /// then we don't match on type unless there are no accept headers. /// /// The sorted accept header values to match. - /// True if not ExcludeMatchOnTypeOnly and accept headers with a q-factor bigger than 0.0 are present. - protected virtual bool ShouldMatchOnType([NotNull] IEnumerable sortedAcceptValues) + /// + /// True if not ExcludeMatchOnTypeOnly and accept headers with a q-factor bigger than 0.0 are present. + /// + protected virtual bool ShouldMatchOnType( + [NotNull] IEnumerable sortedAcceptValues) { return !(ExcludeMatchOnTypeOnly && sortedAcceptValues.Any()); } @@ -376,18 +410,26 @@ namespace System.Net.Http.Formatting /// /// The type to be serialized. /// The formatter we are matching against. - /// A indicating the quality of the match or null is no match. - protected virtual MediaTypeFormatterMatch MatchType([NotNull] Type type, [NotNull] MediaTypeFormatter formatter) + /// + /// A indicating the quality of the match or null is no match. + /// + protected virtual MediaTypeFormatterMatch MatchType( + [NotNull] Type type, + [NotNull] MediaTypeFormatter formatter) { // We already know that we do match on type -- otherwise we wouldn't even be called -- // so this is just a matter of determining how we match. MediaTypeHeaderValue mediaType = null; - List supportedMediaTypes = formatter.SupportedMediaTypes.ToList(); + var supportedMediaTypes = formatter.SupportedMediaTypes.ToList(); if (supportedMediaTypes.Count > 0) { mediaType = supportedMediaTypes[0]; } - return new MediaTypeFormatterMatch(formatter, mediaType, FormattingUtilities.Match, MediaTypeFormatterMatchRanking.MatchOnCanWriteType); + return new MediaTypeFormatterMatch( + formatter, + mediaType, + FormattingUtilities.Match, + MediaTypeFormatterMatchRanking.MatchOnCanWriteType); } /// @@ -396,13 +438,16 @@ namespace System.Net.Http.Formatting /// /// The header values to sort. /// The sorted header values. - protected virtual IEnumerable SortMediaTypeWithQualityHeaderValuesByQFactor(ICollection headerValues) + protected virtual IEnumerable SortMediaTypeWithQualityHeaderValuesByQFactor( + ICollection headerValues) { if (headerValues.Count > 1) { // Use OrderBy() instead of Array.Sort() as it performs fewer comparisons. In this case the comparisons // are quite expensive so OrderBy() performs better. - return headerValues.OrderByDescending(m => m, MediaTypeWithQualityHeaderValueComparer.QualityComparer).ToArray(); + return headerValues + .OrderByDescending(m => m, MediaTypeWithQualityHeaderValueComparer.QualityComparer) + .ToArray(); } else { @@ -411,18 +456,21 @@ namespace System.Net.Http.Formatting } /// - /// Sort Accept-Charset, Accept-Encoding, Accept-Language and related header field values with similar syntax rules - /// (if more than 1) in descending order based on q-factor. + /// Sort Accept-Charset, Accept-Encoding, Accept-Language and related header field values with similar syntax + /// rules (if more than 1) in descending order based on q-factor. /// /// The header values to sort. /// The sorted header values. - protected virtual IEnumerable SortStringWithQualityHeaderValuesByQFactor([NotNull] ICollection headerValues) + protected virtual IEnumerable SortStringWithQualityHeaderValuesByQFactor( + [NotNull] ICollection headerValues) { if (headerValues.Count > 1) { // Use OrderBy() instead of Array.Sort() as it performs fewer comparisons. In this case the comparisons // are quite expensive so OrderBy() performs better. - return headerValues.OrderByDescending(m => m, StringWithQualityHeaderValueComparer.QualityComparer).ToArray(); + return headerValues + .OrderByDescending(m => m, StringWithQualityHeaderValueComparer.QualityComparer) + .ToArray(); } else { @@ -431,10 +479,12 @@ namespace System.Net.Http.Formatting } /// - /// Evaluates whether a match is better than the current match and if so returns the replacement; otherwise returns the - /// current match. + /// Evaluates whether a match is better than the current match and if so returns the replacement; otherwise + /// returns the current match. /// - protected virtual MediaTypeFormatterMatch UpdateBestMatch(MediaTypeFormatterMatch current, MediaTypeFormatterMatch potentialReplacement) + protected virtual MediaTypeFormatterMatch UpdateBestMatch( + MediaTypeFormatterMatch current, + MediaTypeFormatterMatch potentialReplacement) { if (potentialReplacement == null) { @@ -452,7 +502,6 @@ namespace System.Net.Http.Formatting private static MediaTypeFormatter[] GetWritingFormatters(IEnumerable formatters) { Debug.Assert(formatters != null); - MediaTypeFormatterCollection formatterCollection = formatters as MediaTypeFormatterCollection; return formatters.AsArray(); } } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/FormattingUtilities.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/FormattingUtilities.cs index c04277491a..e7b1381c10 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/FormattingUtilities.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/FormattingUtilities.cs @@ -8,10 +8,9 @@ using System.Globalization; using System.Linq; using System.Net.Http.Formatting; using System.Net.Http.Headers; -using System.Runtime.Serialization; +using System.Reflection; using System.Xml; using Newtonsoft.Json.Linq; -using System.Reflection; namespace System.Net.Http { @@ -161,7 +160,9 @@ namespace System.Net.Http /// public static XmlDictionaryReaderQuotas CreateDefaultReaderQuotas() { -#if NETFX_CORE // MaxDepth is a DOS mitigation. We don't support MaxDepth in portable libraries because it is strictly client side. + // MaxDepth is a DOS mitigation. We don't support MaxDepth in portable libraries because it is strictly + // client side. +#if NETFX_CORE return XmlDictionaryReaderQuotas.Max; #else return new XmlDictionaryReaderQuotas() @@ -187,7 +188,9 @@ namespace System.Net.Http return token; } - if (token.StartsWith("\"", StringComparison.Ordinal) && token.EndsWith("\"", StringComparison.Ordinal) && token.Length > 1) + if (token.StartsWith("\"", StringComparison.Ordinal) && + token.EndsWith("\"", StringComparison.Ordinal) && + token.Length > 1) { return token.Substring(1, token.Length - 2); } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/IContentNegotiator.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/IContentNegotiator.cs index 1909f92480..05c37ce25e 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/IContentNegotiator.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/IContentNegotiator.cs @@ -15,20 +15,27 @@ namespace System.Net.Http.Formatting public interface IContentNegotiator { /// - /// Performs content negotiating by selecting the most appropriate out of the passed in - /// for the given that can serialize an object of the given - /// . + /// Performs content negotiating by selecting the most appropriate out of the + /// passed in for the given that can serialize an + /// object of the given . /// /// - /// Implementations of this method should call + /// Implementations of this method should call /// on the selected formatter and return the result of that method. /// /// The type to be serialized. - /// Request message, which contains the header values used to perform negotiation. + /// + /// Request message, which contains the header values used to perform negotiation. + /// /// The set of objects from which to choose. - /// The result of the negotiation containing the most appropriate instance, - /// or null if there is no appropriate formatter. - ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable formatters); + /// + /// The result of the negotiation containing the most appropriate instance, + /// or null if there is no appropriate formatter. + /// + ContentNegotiationResult Negotiate( + Type type, + HttpRequestMessage request, + IEnumerable formatters); } } #endif diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeConstants.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeConstants.cs index d78f6f66aa..82532aecdc 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeConstants.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeConstants.cs @@ -12,13 +12,20 @@ namespace System.Net.Http.Formatting /// internal static class MediaTypeConstants { - private static readonly MediaTypeHeaderValue _defaultApplicationXmlMediaType = new MediaTypeHeaderValue("application/xml"); - private static readonly MediaTypeHeaderValue _defaultTextXmlMediaType = new MediaTypeHeaderValue("text/xml"); - private static readonly MediaTypeHeaderValue _defaultApplicationJsonMediaType = new MediaTypeHeaderValue("application/json"); - private static readonly MediaTypeHeaderValue _defaultTextJsonMediaType = new MediaTypeHeaderValue("text/json"); - private static readonly MediaTypeHeaderValue _defaultApplicationOctetStreamMediaType = new MediaTypeHeaderValue("application/octet-stream"); - private static readonly MediaTypeHeaderValue _defaultApplicationFormUrlEncodedMediaType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); - private static readonly MediaTypeHeaderValue _defaultApplicationBsonMediaType = new MediaTypeHeaderValue("application/bson"); + private static readonly MediaTypeHeaderValue _defaultApplicationXmlMediaType = + new MediaTypeHeaderValue("application/xml"); + private static readonly MediaTypeHeaderValue _defaultTextXmlMediaType = + new MediaTypeHeaderValue("text/xml"); + private static readonly MediaTypeHeaderValue _defaultApplicationJsonMediaType = + new MediaTypeHeaderValue("application/json"); + private static readonly MediaTypeHeaderValue _defaultTextJsonMediaType = + new MediaTypeHeaderValue("text/json"); + private static readonly MediaTypeHeaderValue _defaultApplicationOctetStreamMediaType = + new MediaTypeHeaderValue("application/octet-stream"); + private static readonly MediaTypeHeaderValue _defaultApplicationFormUrlEncodedMediaType = + new MediaTypeHeaderValue("application/x-www-form-urlencoded"); + private static readonly MediaTypeHeaderValue _defaultApplicationBsonMediaType = + new MediaTypeHeaderValue("application/bson"); /// /// Gets a instance representing application/octet-stream. diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeFormatterMatch.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeFormatterMatch.cs index 12739383c1..bd68a8dc9e 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeFormatterMatch.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeFormatterMatch.cs @@ -4,7 +4,6 @@ #if ASPNETCORE50 using System.Net.Http.Headers; -using System.Web.Http; namespace System.Net.Http.Formatting { @@ -17,10 +16,19 @@ namespace System.Net.Http.Formatting /// Initializes a new instance of the class. /// /// The matching formatter. - /// The media type. Can be null in which case the media type application/octet-stream is used. - /// The quality of the match. Can be null in which case it is considered a full match with a value of 1.0 + /// + /// The media type. Can be null in which case the media type application/octet-stream is used. + /// + /// + /// The quality of the match. Can be null in which case it is considered a full match with a value of + /// 1.0. + /// /// The kind of match. - public MediaTypeFormatterMatch(MediaTypeFormatter formatter, MediaTypeHeaderValue mediaType, double? quality, MediaTypeFormatterMatchRanking ranking) + public MediaTypeFormatterMatch( + MediaTypeFormatter formatter, + MediaTypeHeaderValue mediaType, + double? quality, + MediaTypeFormatterMatchRanking ranking) { Formatter = formatter; MediaType = mediaType != null ? mediaType : MediaTypeConstants.ApplicationOctetStreamMediaType; diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeHeaderValueExtensions.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeHeaderValueExtensions.cs index 224e6abc9c..1ad5b95486 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeHeaderValueExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeHeaderValueExtensions.cs @@ -3,12 +3,10 @@ #if ASPNETCORE50 -using Microsoft.AspNet.Mvc; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; -using System.Linq; using System.Net.Http.Headers; +using Microsoft.AspNet.Mvc; namespace System.Net.Http.Formatting { @@ -20,10 +18,11 @@ namespace System.Net.Http.Formatting /// /// Determines whether two instances match. The instance /// is said to match if and only if - /// is a strict subset of the values and parameters of . + /// is a strict subset of the values and parameters of + /// . /// That is, if the media type and media type parameters of are all present - /// and match those of then it is a match even though may have additional - /// parameters. + /// and match those of then it is a match even though + /// may have additional parameters. /// /// The first media type. /// The second media type. @@ -37,16 +36,23 @@ namespace System.Net.Http.Formatting /// /// Determines whether two instances match. The instance /// is said to match if and only if - /// is a strict subset of the values and parameters of . + /// is a strict subset of the values and parameters of + /// . /// That is, if the media type and media type parameters of are all present - /// and match those of then it is a match even though may have additional - /// parameters. + /// and match those of then it is a match even though + /// may have additional parameters. /// /// The first media type. /// The second media type. - /// Indicates whether is a regular media type, a subtype media range, or a full media range + /// + /// Indicates whether is a regular media type, a subtype media range, or a full + /// media range. + /// /// true if this is a subset of ; false otherwise. - public static bool IsSubsetOf(this MediaTypeHeaderValue mediaType1, MediaTypeHeaderValue mediaType2, out MediaTypeHeaderValueRange mediaType2Range) + public static bool IsSubsetOf( + this MediaTypeHeaderValue mediaType1, + MediaTypeHeaderValue mediaType2, + out MediaTypeHeaderValueRange mediaType2Range) { // Performance-sensitive Debug.Assert(mediaType1 != null); @@ -57,8 +63,8 @@ namespace System.Net.Http.Formatting return false; } - ParsedMediaTypeHeaderValue parsedMediaType1 = new ParsedMediaTypeHeaderValue(mediaType1); - ParsedMediaTypeHeaderValue parsedMediaType2 = new ParsedMediaTypeHeaderValue(mediaType2); + var parsedMediaType1 = new ParsedMediaTypeHeaderValue(mediaType1); + var parsedMediaType2 = new ParsedMediaTypeHeaderValue(mediaType2); mediaType2Range = parsedMediaType2.IsAllMediaRange ? MediaTypeHeaderValueRange.AllMediaRange : parsedMediaType2.IsSubtypeMediaRange ? MediaTypeHeaderValueRange.SubtypeMediaRange : MediaTypeHeaderValueRange.None; @@ -80,18 +86,19 @@ namespace System.Net.Http.Formatting // So far we either have a full match or a subset match. Now check that all of // mediaType1's parameters are present and equal in mediatype2 - // Optimize for the common case where the parameters inherit from Collection and cache the count which is faster for Collection. - Collection parameters1 = mediaType1.Parameters.AsCollection(); - int parameterCount1 = parameters1.Count; - Collection parameters2 = mediaType2.Parameters.AsCollection(); - int parameterCount2 = parameters2.Count; - for (int i = 0; i < parameterCount1; i++) + // Optimize for the common case where the parameters inherit from Collection and cache the count which + // is faster for Collection. + var parameters1 = mediaType1.Parameters.AsCollection(); + var parameterCount1 = parameters1.Count; + var parameters2 = mediaType2.Parameters.AsCollection(); + var parameterCount2 = parameters2.Count; + for (var i = 0; i < parameterCount1; i++) { - NameValueHeaderValue parameter1 = parameters1[i]; - bool found = false; - for (int j = 0; j < parameterCount2; j++) + var parameter1 = parameters1[i]; + var found = false; + for (var j = 0; j < parameterCount2; j++) { - NameValueHeaderValue parameter2 = parameters2[j]; + var parameter2 = parameters2[j]; if (parameter1.Equals(parameter2)) { found = true; diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeWithQualityHeaderComparer.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeWithQualityHeaderComparer.cs index 0a8034ac3a..01bafaf31d 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeWithQualityHeaderComparer.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/MediaTypeWithQualityHeaderComparer.cs @@ -15,7 +15,8 @@ namespace System.Net.Http.Formatting /// header field q-values. internal class MediaTypeWithQualityHeaderValueComparer : IComparer { - private static readonly MediaTypeWithQualityHeaderValueComparer _mediaTypeComparer = new MediaTypeWithQualityHeaderValueComparer(); + private static readonly MediaTypeWithQualityHeaderValueComparer _mediaTypeComparer = + new MediaTypeWithQualityHeaderValueComparer(); private MediaTypeWithQualityHeaderValueComparer() { @@ -27,11 +28,12 @@ namespace System.Net.Http.Formatting } /// - /// Compares two based on their quality value (a.k.a their "q-value"). - /// Values with identical q-values are considered equal (i.e the result is 0) with the exception that sub-type wild-cards are - /// considered less than specific media types and full wild-cards are considered less than sub-type wild-cards. This allows to - /// sort a sequence of following their q-values in the order of specific media types, - /// sub-type wildcards, and last any full wild-cards. + /// Compares two based on their quality value (a.k.a their + /// "q-value"). Values with identical q-values are considered equal (i.e the result is 0) with the exception + /// that sub-type wild-cards are considered less than specific media types and full wild-cards are considered + /// less than sub-type wild-cards. This allows to sort a sequence of + /// following their q-values in the order of specific media types, subtype wild-cards, and last any full + /// wild-cards. /// /// The first to compare. /// The second to compare. @@ -46,12 +48,11 @@ namespace System.Net.Http.Formatting return 0; } - int returnValue = CompareBasedOnQualityFactor(mediaType1, mediaType2); - + var returnValue = CompareBasedOnQualityFactor(mediaType1, mediaType2); if (returnValue == 0) { - ParsedMediaTypeHeaderValue parsedMediaType1 = new ParsedMediaTypeHeaderValue(mediaType1); - ParsedMediaTypeHeaderValue parsedMediaType2 = new ParsedMediaTypeHeaderValue(mediaType2); + var parsedMediaType1 = new ParsedMediaTypeHeaderValue(mediaType1); + var parsedMediaType2 = new ParsedMediaTypeHeaderValue(mediaType2); if (!parsedMediaType1.TypesEqual(ref parsedMediaType2)) { @@ -88,14 +89,16 @@ namespace System.Net.Http.Formatting return returnValue; } - private static int CompareBasedOnQualityFactor(MediaTypeWithQualityHeaderValue mediaType1, MediaTypeWithQualityHeaderValue mediaType2) + private static int CompareBasedOnQualityFactor( + MediaTypeWithQualityHeaderValue mediaType1, + MediaTypeWithQualityHeaderValue mediaType2) { Debug.Assert(mediaType1 != null); Debug.Assert(mediaType2 != null); - double mediaType1Quality = mediaType1.Quality ?? FormattingUtilities.Match; - double mediaType2Quality = mediaType2.Quality ?? FormattingUtilities.Match; - double qualityDifference = mediaType1Quality - mediaType2Quality; + var mediaType1Quality = mediaType1.Quality ?? FormattingUtilities.Match; + var mediaType2Quality = mediaType2.Quality ?? FormattingUtilities.Match; + var qualityDifference = mediaType1Quality - mediaType2Quality; if (qualityDifference < 0) { return -1; diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/ParsedMediaTypeHeaderValue.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/ParsedMediaTypeHeaderValue.cs index 6e3f4c2214..19d9b51724 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/ParsedMediaTypeHeaderValue.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/ParsedMediaTypeHeaderValue.cs @@ -8,7 +8,7 @@ using System.Net.Http.Headers; namespace System.Net.Http.Formatting { - // This type is instanciated by frequently called comparison methods so is very performance sensitive + // This type is instantiated by frequently called comparison methods so is very performance sensitive internal struct ParsedMediaTypeHeaderValue { private const char MediaRangeAsterisk = '*'; @@ -22,13 +22,15 @@ namespace System.Net.Http.Formatting public ParsedMediaTypeHeaderValue(MediaTypeHeaderValue mediaTypeHeaderValue) { Debug.Assert(mediaTypeHeaderValue != null); - string mediaType = _mediaType = mediaTypeHeaderValue.MediaType; + var mediaType = _mediaType = mediaTypeHeaderValue.MediaType; _delimiterIndex = mediaType.IndexOf(MediaTypeSubtypeDelimiter); - Debug.Assert(_delimiterIndex > 0, "The constructor of the MediaTypeHeaderValue would have failed if there wasn't a type and subtype."); + Debug.Assert( + _delimiterIndex > 0, + "The constructor of the MediaTypeHeaderValue would have failed if there wasn't a type and subtype."); _isAllMediaRange = false; _isSubtypeMediaRange = false; - int mediaTypeLength = mediaType.Length; + var mediaTypeLength = mediaType.Length; if (_delimiterIndex == mediaTypeLength - 2) { if (mediaType[mediaTypeLength - 1] == MediaRangeAsterisk) @@ -58,17 +60,31 @@ namespace System.Net.Http.Formatting { return false; } - return String.Compare(_mediaType, 0, other._mediaType, 0, _delimiterIndex, StringComparison.OrdinalIgnoreCase) == 0; + + return string.Compare( + strA: _mediaType, + indexA: 0, + strB: other._mediaType, + indexB: 0, + length: _delimiterIndex, + comparisonType: StringComparison.OrdinalIgnoreCase) == 0; } public bool SubTypesEqual(ref ParsedMediaTypeHeaderValue other) { - int _subTypeLength = _mediaType.Length - _delimiterIndex - 1; + var _subTypeLength = _mediaType.Length - _delimiterIndex - 1; if (_subTypeLength != other._mediaType.Length - other._delimiterIndex - 1) { return false; } - return String.Compare(_mediaType, _delimiterIndex + 1, other._mediaType, other._delimiterIndex + 1, _subTypeLength, StringComparison.OrdinalIgnoreCase) == 0; + + return string.Compare( + strA: _mediaType, + indexA: _delimiterIndex + 1, + strB: other._mediaType, + indexB: other._delimiterIndex + 1, + length: _subTypeLength, + comparisonType: StringComparison.OrdinalIgnoreCase) == 0; } } } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/StringWithQualityHeaderValueComparer.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/StringWithQualityHeaderValueComparer.cs index e210572a59..9551bc2132 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/StringWithQualityHeaderValueComparer.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ContentNegotiator/StringWithQualityHeaderValueComparer.cs @@ -31,10 +31,10 @@ namespace System.Net.Http.Formatting } /// - /// Compares two based on their quality value (a.k.a their "q-value"). - /// Values with identical q-values are considered equal (i.e the result is 0) with the exception of wild-card - /// values (i.e. a value of "*") which are considered less than non-wild-card values. This allows to sort - /// a sequence of following their q-values ending up with any + /// Compares two based on their quality value (a.k.a their + /// "q-value"). Values with identical q-values are considered equal (i.e the result is 0) with the exception of + /// wild-card values (i.e. a value of "*") which are considered less than non-wild-card values. This allows to + /// sort a sequence of following their q-values ending up with any /// wild-cards at the end. /// /// The first value to compare. @@ -46,9 +46,9 @@ namespace System.Net.Http.Formatting Debug.Assert(stringWithQuality1 != null); Debug.Assert(stringWithQuality2 != null); - double quality1 = stringWithQuality1.Quality ?? FormattingUtilities.Match; - double quality2 = stringWithQuality2.Quality ?? FormattingUtilities.Match; - double qualityDifference = quality1 - quality2; + var quality1 = stringWithQuality1.Quality ?? FormattingUtilities.Match; + var quality2 = stringWithQuality2.Quality ?? FormattingUtilities.Match; + var qualityDifference = quality1 - quality2; if (qualityDifference < 0) { return -1; @@ -58,7 +58,7 @@ namespace System.Net.Http.Formatting return 1; } - if (!String.Equals(stringWithQuality1.Value, stringWithQuality2.Value, StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(stringWithQuality1.Value, stringWithQuality2.Value, StringComparison.OrdinalIgnoreCase)) { if (String.Equals(stringWithQuality1.Value, "*", StringComparison.Ordinal)) { diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/FormDataCollectionExtensions.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/FormDataCollectionExtensions.cs index 28e99b6c8a..2520cb1998 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/FormDataCollectionExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/FormDataCollectionExtensions.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim var i = 0; while (true) { - int indexOpen = key.IndexOf('[', i); + var indexOpen = key.IndexOf('[', i); if (indexOpen < 0) { // Fast path, no normalization needed. diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpError.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpError.cs index 4adeec0ead..5e038f0978 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpError.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpError.cs @@ -29,7 +29,8 @@ namespace System.Web.Http } /// - /// Initializes a new instance of the class containing error message . + /// Initializes a new instance of the class containing error message + /// . /// /// The error message to associate with this instance. public HttpError([NotNull] string message) @@ -42,7 +43,9 @@ namespace System.Web.Http /// Initializes a new instance of the class for . /// /// The exception to use for error information. - /// true to include the exception information in the error; false otherwise + /// + /// true to include the exception information in the error;false otherwise. + /// public HttpError([NotNull] Exception exception, bool includeErrorDetail) : this() { @@ -64,7 +67,9 @@ namespace System.Web.Http /// Initializes a new instance of the class for . /// /// The invalid model state to use for error information. - /// true to include exception messages in the error; false otherwise + /// + /// true to include exception messages in the error; false otherwise. + /// public HttpError([NotNull] ModelStateDictionary modelState, bool includeErrorDetail) : this() { @@ -105,8 +110,9 @@ namespace System.Web.Http /// /// The high-level, user-visible message explaining the cause of the error. Information carried in this field - /// should be considered public in that it will go over the wire regardless of the value of error detail policy. - /// As a result care should be taken not to disclose sensitive information about the server or the application. + /// should be considered public in that it will go over the wire regardless of the value of error detail + /// policy. As a result care should be taken not to disclose sensitive information about the server or the + /// application. /// public string Message { diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageExtensions.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageExtensions.cs index 6100bc140c..8fdf16204c 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageExtensions.cs @@ -20,19 +20,22 @@ namespace System.Net.Http /// public static class HttpRequestMessageExtensions { - #if !ASPNETCORE50 /// - /// Helper method for creating an message with a "416 (Requested Range Not Satisfiable)" status code. - /// This response can be used in combination with the to indicate that the requested range or - /// ranges do not overlap with the current resource. The response contains a "Content-Range" header indicating the valid upper and lower - /// bounds for requested ranges. + /// Helper method for creating an message with a "416 (Requested Range Not + /// Satisfiable)" status code. This response can be used in combination with the + /// to indicate that the requested range or + /// ranges do not overlap with the current resource. The response contains a "Content-Range" header indicating + /// the valid upper and lower bounds for requested ranges. /// /// The request. - /// An instance, typically thrown by a - /// instance. - /// An 416 (Requested Range Not Satisfiable) error response with a Content-Range header indicating the valid range. + /// An instance, typically + /// thrown by a instance. + /// + /// An 416 (Requested Range Not Satisfiable) error response with a Content-Range header indicating the valid + /// range. + /// public static HttpResponseMessage CreateErrorResponse( [NotNull] this HttpRequestMessage request, [NotNull] InvalidByteRangeException invalidByteRangeException) @@ -47,9 +50,10 @@ namespace System.Net.Http #endif /// - /// Helper method that performs content negotiation and creates a representing an error - /// with an instance of wrapping an with message . - /// If no formatter is found, this method returns a response with status 406 NotAcceptable. + /// Helper method that performs content negotiation and creates a + /// representing an error with an instance of wrapping an + /// with message . If no formatter is found, this method + /// returns a response with status 406 NotAcceptable. /// /// /// This method requires that has been associated with an instance of @@ -58,7 +62,10 @@ namespace System.Net.Http /// The request. /// The status code of the created response. /// The error message. - /// An error response with error message and status code . + /// + /// An error response with error message and status code + /// . + /// public static HttpResponseMessage CreateErrorResponse( [NotNull] this HttpRequestMessage request, HttpStatusCode statusCode, @@ -68,9 +75,11 @@ namespace System.Net.Http } /// - /// Helper method that performs content negotiation and creates a representing an error - /// with an instance of wrapping an with error message - /// for exception . If no formatter is found, this method returns a response with status 406 NotAcceptable. + /// Helper method that performs content negotiation and creates a + /// representing an error with an instance of wrapping an + /// with error message for exception + /// . If no formatter is found, this method returns a response with status 406 + /// NotAcceptable. /// /// /// This method requires that has been associated with an instance of @@ -93,9 +102,10 @@ namespace System.Net.Http } /// - /// Helper method that performs content negotiation and creates a representing an error - /// with an instance of wrapping an for exception . - /// If no formatter is found, this method returns a response with status 406 NotAcceptable. + /// Helper method that performs content negotiation and creates a + /// representing an error with an instance of wrapping an + /// for exception . If no formatter is found, this method + /// returns a response with status 406 NotAcceptable. /// /// /// This method requires that has been associated with an instance of @@ -104,7 +114,9 @@ namespace System.Net.Http /// The request. /// The status code of the created response. /// The exception. - /// An error response for with status code . + /// + /// An error response for with status code . + /// public static HttpResponseMessage CreateErrorResponse( [NotNull] this HttpRequestMessage request, HttpStatusCode statusCode, @@ -114,9 +126,10 @@ namespace System.Net.Http } /// - /// Helper method that performs content negotiation and creates a representing an error - /// with an instance of wrapping an for model state . - /// If no formatter is found, this method returns a response with status 406 NotAcceptable. + /// Helper method that performs content negotiation and creates a + /// representing an error with an instance of wrapping an + /// for model state . If no formatter is found, this + /// method returns a response with status 406 NotAcceptable. /// /// /// This method requires that has been associated with an instance of @@ -125,7 +138,9 @@ namespace System.Net.Http /// The request. /// The status code of the created response. /// The model state. - /// An error response for with status code . + /// + /// An error response for with status code . + /// public static HttpResponseMessage CreateErrorResponse( [NotNull] this HttpRequestMessage request, HttpStatusCode statusCode, @@ -135,9 +150,9 @@ namespace System.Net.Http } /// - /// Helper method that performs content negotiation and creates a representing an error - /// with an instance of wrapping as the content. If no formatter - /// is found, this method returns a response with status 406 NotAcceptable. + /// Helper method that performs content negotiation and creates a + /// representing an error with an instance of wrapping + /// as the content. If no formatter is found, this method returns a response with status 406 NotAcceptable. /// /// /// This method requires that has been associated with an instance of @@ -146,7 +161,9 @@ namespace System.Net.Http /// The request. /// The status code of the created response. /// The error to wrap. - /// An error response wrapping with status code . + /// + /// An error response wrapping with status code . + /// public static HttpResponseMessage CreateErrorResponse( [NotNull] this HttpRequestMessage request, HttpStatusCode statusCode, @@ -156,9 +173,10 @@ namespace System.Net.Http } /// - /// Helper method that performs content negotiation and creates a with an instance - /// of as the content and as the status code - /// if a formatter can be found. If no formatter is found, this method returns a response with status 406 NotAcceptable. + /// Helper method that performs content negotiation and creates a with an + /// instance of as the content and + /// as the status code if a formatter can be found. If no formatter is found, this method returns a response + /// with status 406 NotAcceptable. /// /// /// This method requires that has been associated with an instance of @@ -167,16 +185,18 @@ namespace System.Net.Http /// The type of the value. /// The request. /// The value to wrap. Can be null. - /// A response wrapping with status code. + /// + /// A response wrapping with status code. + /// public static HttpResponseMessage CreateResponse([NotNull] this HttpRequestMessage request, T value) { return request.CreateResponse(HttpStatusCode.OK, value, formatters: null); } /// - /// Helper method that performs content negotiation and creates a with an instance - /// of as the content if a formatter can be found. If no formatter is found, this - /// method returns a response with status 406 NotAcceptable. + /// Helper method that performs content negotiation and creates a with an + /// instance of as the content if a formatter can be found. If no formatter is + /// found, this method returns a response with status 406 NotAcceptable. /// configuration. /// /// @@ -188,15 +208,18 @@ namespace System.Net.Http /// The status code of the created response. /// The value to wrap. Can be null. /// A response wrapping with . - public static HttpResponseMessage CreateResponse(this HttpRequestMessage request, HttpStatusCode statusCode, T value) + public static HttpResponseMessage CreateResponse( + this HttpRequestMessage request, + HttpStatusCode statusCode, + T value) { return request.CreateResponse(statusCode, value, formatters: null); } /// - /// Helper method that performs content negotiation and creates a with an instance - /// of as the content if a formatter can be found. If no formatter is found, this - /// method returns a response with status 406 NotAcceptable. + /// Helper method that performs content negotiation and creates a with an + /// instance of as the content if a formatter can be found. If no formatter is + /// found, this method returns a response with status 406 NotAcceptable. /// /// /// This method will use the provided or it will get the @@ -238,29 +261,39 @@ namespace System.Net.Http } /// - /// Helper method that creates a with an instance containing the provided - /// . The given is used to find an instance of . + /// Helper method that creates a with an + /// instance containing the provided . The given is used + /// to find an instance of . /// /// The type of the value. /// The request. /// The status code of the created response. /// The value to wrap. Can be null. - /// The media type used to look up an instance of . + /// + /// The media type used to look up an instance of . + /// /// A response wrapping with . - public static HttpResponseMessage CreateResponse(this HttpRequestMessage request, HttpStatusCode statusCode, T value, string mediaType) + public static HttpResponseMessage CreateResponse( + this HttpRequestMessage request, + HttpStatusCode statusCode, + T value, + string mediaType) { return request.CreateResponse(statusCode, value, new MediaTypeHeaderValue(mediaType)); } /// - /// Helper method that creates a with an instance containing the provided - /// . The given is used to find an instance of . + /// Helper method that creates a with an + /// instance containing the provided . The given is used + /// to find an instance of . /// /// The type of the value. /// The request. /// The status code of the created response. /// The value to wrap. Can be null. - /// The media type used to look up an instance of . + /// + /// The media type used to look up an instance of . + /// /// A response wrapping with . public static HttpResponseMessage CreateResponse( [NotNull] this HttpRequestMessage request, @@ -287,8 +320,8 @@ namespace System.Net.Http } /// - /// Helper method that creates a with an instance containing the provided - /// and the given . + /// Helper method that creates a with an + /// instance containing the provided and the given . /// /// The type of the value. /// The request. @@ -306,15 +339,17 @@ namespace System.Net.Http } /// - /// Helper method that creates a with an instance containing the provided - /// and the given . + /// Helper method that creates a with an + /// instance containing the provided and the given . /// /// The type of the value. /// The request. /// The status code of the created response. /// The value to wrap. Can be null. /// The formatter to use. - /// The media type override to set on the response's content. Can be null. + /// + /// The media type override to set on the response's content. Can be null. + /// /// A response wrapping with . public static HttpResponseMessage CreateResponse( [NotNull] this HttpRequestMessage request, @@ -328,15 +363,17 @@ namespace System.Net.Http } /// - /// Helper method that creates a with an instance containing the provided - /// and the given . + /// Helper method that creates a with an + /// instance containing the provided and the given . /// /// The type of the value. /// The request. /// The status code of the created response. /// The value to wrap. Can be null. /// The formatter to use. - /// The media type override to set on the response's content. Can be null. + /// + /// The media type override to set on the response's content. Can be null. + /// /// A response wrapping with . public static HttpResponseMessage CreateResponse( [NotNull] this HttpRequestMessage request, diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/WebApiCompatShimOptionsSetup.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/WebApiCompatShimOptionsSetup.cs index c9c26e7cc2..e4f774386f 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/WebApiCompatShimOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/WebApiCompatShimOptionsSetup.cs @@ -6,9 +6,10 @@ using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.WebApiCompatShim { - public class WebApiCompatShimOptionsSetup : IConfigureOptions, IConfigureOptions + public class WebApiCompatShimOptionsSetup + : IConfigureOptions, IConfigureOptions { - public readonly static string DefaultAreaName = "api"; + public static readonly string DefaultAreaName = "api"; public int Order { diff --git a/src/Microsoft.AspNet.Mvc/IAfterCompileContext.cs b/src/Microsoft.AspNet.Mvc/IAfterCompileContext.cs new file mode 100644 index 0000000000..b5a5b296c1 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc/IAfterCompileContext.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.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; + +namespace Microsoft.Framework.Runtime +{ + [AssemblyNeutral] + public interface IAfterCompileContext + { + CSharpCompilation CSharpCompilation { get; set; } + + IList Diagnostics { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc/ICompileModule.cs b/src/Microsoft.AspNet.Mvc/ICompileModule.cs new file mode 100644 index 0000000000..2ae33db743 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc/ICompileModule.cs @@ -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.Framework.Runtime +{ + [AssemblyNeutral] + public interface ICompileModule + { + void BeforeCompile(IBeforeCompileContext context); + + void AfterCompile(IAfterCompileContext context); + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index 6c7b832146..64c9ec3d3d 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -26,19 +26,19 @@ namespace Microsoft.AspNet.Mvc { var describe = new ServiceDescriber(configuration); - // // Options and core services. - // + yield return describe.Transient, MvcOptionsSetup>(); yield return describe.Transient, RazorViewEngineOptionsSetup>(); yield return describe.Transient(); yield return describe.Transient(typeof(INestedProviderManager<>), typeof(NestedProviderManager<>)); - yield return describe.Transient(typeof(INestedProviderManagerAsync<>), typeof(NestedProviderManagerAsync<>)); + yield return describe.Transient( + typeof(INestedProviderManagerAsync<>), + typeof(NestedProviderManagerAsync<>)); yield return describe.Transient(); - // // Core action discovery, filters and action execution. - // + // These are consumed only when creating action descriptors, then they can be de-allocated yield return describe.Transient(); yield return describe.Transient(); @@ -74,9 +74,8 @@ namespace Microsoft.AspNet.Mvc yield return describe.Transient, DefaultFilterProvider>(); - // // Dataflow - ModelBinding, Validation and Formatting - // + yield return describe.Transient(); yield return describe.Scoped(); @@ -93,11 +92,10 @@ namespace Microsoft.AspNet.Mvc yield return describe.Transient(); yield return describe.Scoped(); yield return describe.Transient(); - yield return describe.Transient(); + yield return describe.Transient(); - // // Razor, Views and runtime compilation - // // The provider is inexpensive to initialize and provides ViewEngines that may require request // specific services. @@ -135,9 +133,7 @@ namespace Microsoft.AspNet.Mvc // Virtual path view factory needs to stay scoped so views can get get scoped services. yield return describe.Scoped(); - // // View and rendering helpers - // yield return describe.Transient(); yield return describe.Transient(typeof(IHtmlHelper<>), typeof(HtmlHelper<>)); @@ -157,9 +153,7 @@ namespace Microsoft.AspNet.Mvc DefaultViewComponentInvokerProvider>(); yield return describe.Transient(); - // // Security and Authorization - // yield return describe.Transient(); yield return describe.Singleton(); @@ -167,9 +161,7 @@ namespace Microsoft.AspNet.Mvc yield return describe.Singleton(); - // // Api Description - // yield return describe.Singleton(); diff --git a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs index e4f5298c1a..d749009194 100644 --- a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs +++ b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs @@ -48,7 +48,9 @@ namespace Microsoft.AspNet.Mvc } // TODO: KILL THIS - private static IServiceProvider BuildFallbackServiceProvider(IEnumerable services, IServiceProvider fallback) + private static IServiceProvider BuildFallbackServiceProvider( + IEnumerable services, + IServiceProvider fallback) { var sc = HostingServices.Create(fallback); sc.Add(services); @@ -58,7 +60,8 @@ namespace Microsoft.AspNet.Mvc && t.ServiceType != typeof(IServiceManifest) && t.ServiceType != typeof(IServiceProvider)) .Select(t => t.ServiceType).Distinct(); - sc.AddInstance(new ServiceManifest(manifestTypes, fallback.GetRequiredService())); + sc.AddInstance( + new ServiceManifest(manifestTypes, fallback.GetRequiredService())); return sc.BuildServiceProvider(); } @@ -77,22 +80,3 @@ namespace Microsoft.AspNet.Mvc } } } - -namespace Microsoft.Framework.Runtime -{ - [AssemblyNeutral] - public interface ICompileModule - { - void BeforeCompile(IBeforeCompileContext context); - - void AfterCompile(IAfterCompileContext context); - } - - [AssemblyNeutral] - public interface IAfterCompileContext - { - CSharpCompilation CSharpCompilation { get; set; } - - IList Diagnostics { get; } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs index d6ee18e53a..91e33776d9 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs @@ -994,17 +994,16 @@ namespace Microsoft.AspNet.Mvc.Test // Assert binder.Verify(); } - + [Fact] public async Task TryUpdateModel_PredicateOverload_UsesPassedArguments() { // Arrange var modelName = "mymodel"; - Func includePredicate = - (context, propertyName) => - string.Equals(propertyName, "include1", StringComparison.OrdinalIgnoreCase) || - string.Equals(propertyName, "include2", StringComparison.OrdinalIgnoreCase); + Func includePredicate = (context, propertyName) => + string.Equals(propertyName, "include1", StringComparison.OrdinalIgnoreCase) || + string.Equals(propertyName, "include2", StringComparison.OrdinalIgnoreCase); var binder = new Mock(); var valueProvider = Mock.Of(); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs index 07f076ce18..db3158b473 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ViewComponents/ViewViewComponentResultTest.cs @@ -227,7 +227,7 @@ namespace Microsoft.AspNet.Mvc var viewEngine = new Mock(MockBehavior.Strict); viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) .Returns(ViewEngineResult.NotFound( - "Components/Object/some-view", + "Components/Object/some-view", new[] { "view-location1", "view-location2" })) .Verifiable(); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs index 332e5b1d8c..5842d66890 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingTests.cs @@ -1056,7 +1056,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Assert var user = JsonConvert.DeserializeObject(response); - // Should not update any not explicitly mentioned properties. + // Should not update any not explicitly mentioned properties. Assert.NotEqual("SomeName", user.UserName); Assert.NotEqual(123, user.Key); @@ -1079,7 +1079,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Assert var user = JsonConvert.DeserializeObject(response); - // Should not update any not explicitly mentioned properties. + // Should not update any not explicitly mentioned properties. Assert.Equal("SomeName", user.UserName); Assert.Equal(123, user.Key); diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeConstants.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeConstants.cs index 191406571b..f7a6e50d43 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeConstants.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeConstants.cs @@ -10,13 +10,20 @@ namespace System.Net.Http.Formatting /// internal static class MediaTypeConstants { - private static readonly MediaTypeHeaderValue _defaultApplicationXmlMediaType = new MediaTypeHeaderValue("application/xml"); - private static readonly MediaTypeHeaderValue _defaultTextXmlMediaType = new MediaTypeHeaderValue("text/xml"); - private static readonly MediaTypeHeaderValue _defaultApplicationJsonMediaType = new MediaTypeHeaderValue("application/json"); - private static readonly MediaTypeHeaderValue _defaultTextJsonMediaType = new MediaTypeHeaderValue("text/json"); - private static readonly MediaTypeHeaderValue _defaultApplicationOctetStreamMediaType = new MediaTypeHeaderValue("application/octet-stream"); - private static readonly MediaTypeHeaderValue _defaultApplicationFormUrlEncodedMediaType = new MediaTypeHeaderValue("application/x-www-form-urlencoded"); - private static readonly MediaTypeHeaderValue _defaultApplicationBsonMediaType = new MediaTypeHeaderValue("application/bson"); + private static readonly MediaTypeHeaderValue _defaultApplicationXmlMediaType = + new MediaTypeHeaderValue("application/xml"); + private static readonly MediaTypeHeaderValue _defaultTextXmlMediaType = + new MediaTypeHeaderValue("text/xml"); + private static readonly MediaTypeHeaderValue _defaultApplicationJsonMediaType = + new MediaTypeHeaderValue("application/json"); + private static readonly MediaTypeHeaderValue _defaultTextJsonMediaType = + new MediaTypeHeaderValue("text/json"); + private static readonly MediaTypeHeaderValue _defaultApplicationOctetStreamMediaType = + new MediaTypeHeaderValue("application/octet-stream"); + private static readonly MediaTypeHeaderValue _defaultApplicationFormUrlEncodedMediaType = + new MediaTypeHeaderValue("application/x-www-form-urlencoded"); + private static readonly MediaTypeHeaderValue _defaultApplicationBsonMediaType = + new MediaTypeHeaderValue("application/bson"); /// /// Gets a instance representing application/octet-stream. diff --git a/test/WebSites/ModelBindingWebSite/Controllers/FromAttributesController.cs b/test/WebSites/ModelBindingWebSite/Controllers/FromAttributesController.cs index 85d9d060a5..5f9a554447 100644 --- a/test/WebSites/ModelBindingWebSite/Controllers/FromAttributesController.cs +++ b/test/WebSites/ModelBindingWebSite/Controllers/FromAttributesController.cs @@ -44,7 +44,7 @@ namespace ModelBindingWebSite.Controllers } // User_FromForm has a FromForm property. - public User_FromForm MultipleFromFormParameterAndProperty(User_FromForm user, + public User_FromForm MultipleFromFormParameterAndProperty(User_FromForm user, [FromForm] Address defaultAddress) { user.HomeAddress = defaultAddress; diff --git a/test/WebSites/ModelBindingWebSite/Models/Department.cs b/test/WebSites/ModelBindingWebSite/Models/Department.cs index 238e6f7894..249ce62aca 100644 --- a/test/WebSites/ModelBindingWebSite/Models/Department.cs +++ b/test/WebSites/ModelBindingWebSite/Models/Department.cs @@ -5,7 +5,7 @@ namespace ModelBindingWebSite { public class Department { - // A single property marked with a binder metadata attribute makes it a binder metadata poco. + // A single property marked with a binder metadata attribute makes it a binder metadata poco. [FromTest] public string Name { get; set; } } diff --git a/test/WebSites/ModelBindingWebSite/Models/MixedUser_FromBody.cs b/test/WebSites/ModelBindingWebSite/Models/MixedUser_FromBody.cs index 8538419cd2..bf3eea2d3c 100644 --- a/test/WebSites/ModelBindingWebSite/Models/MixedUser_FromBody.cs +++ b/test/WebSites/ModelBindingWebSite/Models/MixedUser_FromBody.cs @@ -16,7 +16,7 @@ namespace ModelBindingWebSite [FromQuery] public Address ShippingAddress { get; set; } - // Should get it from the first value provider which + // Should get it from the first value provider which // can provide values for this. public Address DefaultAddress { get; set; } } From 52a984c52d643b8d7fae3e003b8b000fe3c7ca97 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Wed, 7 Jan 2015 17:53:35 -0800 Subject: [PATCH 045/118] react to aspnet/HttpAbstractions#146 changes --- .../AntiForgery/AntiForgeryTokenStore.cs | 2 +- .../FormValueProviderFactory.cs | 2 +- .../AntiXsrf/AntiForgeryTokenStoreTest.cs | 25 +++++++++---------- .../Logging/LoggingExtensions.cs | 2 +- .../FormValueProviderFactoryTests.cs | 4 +-- ...adableStringCollectionValueProviderTest.cs | 2 +- 6 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs index d9576fe4dc..4395181cd4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryTokenStore.cs @@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Mvc public async Task GetFormTokenAsync(HttpContext httpContext) { - var form = await httpContext.Request.GetFormAsync(); + var form = await httpContext.Request.ReadFormAsync(); var value = form[_config.FormFieldName]; if (string.IsNullOrEmpty(value)) { diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs index 7c3a4b282c..a46d7115b3 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/ValueProviders/FormValueProviderFactory.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { var culture = GetCultureInfo(request); return new ReadableStringCollectionValueProvider( - () => request.GetFormAsync(), + async () => await request.ReadFormAsync(), culture); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryTokenStoreTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryTokenStoreTest.cs index 6f66ff63a6..650efd2c82 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryTokenStoreTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryTokenStoreTest.cs @@ -164,13 +164,12 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Arrange var mockHttpContext = new Mock(); var requestContext = new Mock(); - IReadableStringCollection formsCollection = - new MockCookieCollection(new Dictionary() { { "form-field-name", string.Empty } }); - requestContext.Setup(o => o.GetFormAsync(CancellationToken.None)) - .Returns(Task.FromResult(formsCollection)); + var formCollection = new Mock(); + formCollection.Setup(f => f["form-field-name"]).Returns(string.Empty); + requestContext.Setup(o => o.ReadFormAsync(CancellationToken.None)) + .Returns(Task.FromResult(formCollection.Object)); mockHttpContext.Setup(o => o.Request) .Returns(requestContext.Object); - var config = new AntiForgeryOptions() { FormFieldName = "form-field-name" @@ -191,12 +190,12 @@ namespace Microsoft.AspNet.Mvc.Core.Test public async Task GetFormToken_FormFieldIsInvalid_PropagatesException() { // Arrange - IReadableStringCollection formsCollection = - new MockCookieCollection(new Dictionary() { { "form-field-name", "invalid-value" } }); + var formCollection = new Mock(); + formCollection.Setup(f => f["form-field-name"]).Returns("invalid-value"); var requestContext = new Mock(); - requestContext.Setup(o => o.GetFormAsync(CancellationToken.None)) - .Returns(Task.FromResult(formsCollection)); + requestContext.Setup(o => o.ReadFormAsync(CancellationToken.None)) + .Returns(Task.FromResult(formCollection.Object)); var mockHttpContext = new Mock(); mockHttpContext.Setup(o => o.Request) @@ -233,10 +232,10 @@ namespace Microsoft.AspNet.Mvc.Core.Test // Arrange var mockHttpContext = new Mock(); var requestContext = new Mock(); - IReadableStringCollection formsCollection = - new MockCookieCollection(new Dictionary() { { "form-field-name", "valid-value" } }); - requestContext.Setup(o => o.GetFormAsync(CancellationToken.None)) - .Returns(Task.FromResult(formsCollection)); + var formCollection = new Mock(); + formCollection.Setup(f => f["form-field-name"]).Returns("valid-value"); + requestContext.Setup(o => o.ReadFormAsync(CancellationToken.None)) + .Returns(Task.FromResult(formCollection.Object)); mockHttpContext.Setup(o => o.Request) .Returns(requestContext.Object); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingExtensions.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingExtensions.cs index e647a38c60..aeed866057 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingExtensions.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingExtensions.cs @@ -155,7 +155,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { var queryString = QueryHelpers.ParseQuery(query); - return queryString[key]; + return queryString[key].FirstOrDefault(); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs index 4796dc7005..a28d096ced 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/FormValueProviderFactoryTests.cs @@ -48,9 +48,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test private static ValueProviderFactoryContext CreateContext(string contentType) { - var collection = Mock.Of(); + var collection = Mock.Of(); var request = new Mock(); - request.Setup(f => f.GetFormAsync(CancellationToken.None)).Returns(Task.FromResult(collection)); + request.Setup(f => f.ReadFormAsync(CancellationToken.None)).Returns(Task.FromResult(collection)); request.SetupGet(r => r.ContentType).Returns(contentType); var context = new Mock(); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTest.cs index d878f4e1a3..ff6440dc66 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/ValueProviders/ReadableStringCollectionValueProviderTest.cs @@ -6,7 +6,7 @@ using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http; -using Microsoft.AspNet.WebUtilities.Collections; +using Microsoft.AspNet.PipelineCore.Collections; using Xunit; namespace Microsoft.AspNet.Mvc.ModelBinding.Test From f8cb519c2f291c22dff3248e5edea18f51bd938c Mon Sep 17 00:00:00 2001 From: Youngjune Hong Date: Fri, 2 Jan 2015 13:27:22 -0800 Subject: [PATCH 046/118] Adding the functional tests for MvcTagHelpers (1) --- Mvc.sln | 15 +++ .../AntiForgeryTestHelper.cs | 6 +- ...rsWebSite.MvcTagHelper_Customer.Index.html | 44 +++++++++ ...ersWebSite.MvcTagHelper_Home.Customer.html | 42 ++++++++ ...elpersWebSite.MvcTagHelper_Home.Index.html | 76 +++++++++++++++ ...elpersWebSite.MvcTagHelper_Home.Order.html | 79 +++++++++++++++ ...persWebSite.MvcTagHelper_Home.Product.html | 23 +++++ .../MvcTagHelpersTests.cs | 92 ++++++++++++++++++ .../project.json | 1 + .../MvcTagHelper_CustomerController.cs | 17 ++++ .../MvcTagHelper_HomeController.cs | 74 ++++++++++++++ .../MvcTagHelper_OrderController.cs | 17 ++++ .../MvcTagHelper_ProductController.cs | 27 +++++ .../MvcTagHelpersWebSite/Models/Customer.cs | 56 +++++++++++ .../MvcTagHelpersWebSite/Models/Gender.cs | 11 +++ .../MvcTagHelpersWebSite/Models/Order.cs | 59 +++++++++++ .../MvcTagHelpersWebSite/Models/Product.cs | 43 ++++++++ .../MvcTagHelpersWebSite.kproj | 29 ++++++ test/WebSites/MvcTagHelpersWebSite/Startup.cs | 38 ++++++++ .../Views/MvcTagHelper_Home/Index.cshtml | 91 +++++++++++++++++ .../Views/MvcTagHelper_Home/Order.cshtml | 83 ++++++++++++++++ .../Views/MvcTagHelper_Home/Product.cshtml | 27 +++++ .../Views/Shared/Customer.cshtml | 46 +++++++++ .../MvcTagHelpersWebSite/project.json | 19 ++++ .../MvcTagHelpersWebSite/wwwroot/readme.md | Bin 0 -> 30 bytes 25 files changed, 1013 insertions(+), 2 deletions(-) create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Customer.Index.html create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Customer.html create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Index.html create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Order.html create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Product.html create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Areas/Customer/Controllers/MvcTagHelper_CustomerController.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_OrderController.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_ProductController.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Models/Customer.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Models/Gender.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Models/Order.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Models/Product.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/MvcTagHelpersWebSite.kproj create mode 100644 test/WebSites/MvcTagHelpersWebSite/Startup.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Index.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Order.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Product.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Shared/Customer.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/project.json create mode 100644 test/WebSites/MvcTagHelpersWebSite/wwwroot/readme.md diff --git a/Mvc.sln b/Mvc.sln index 5c1f9b359a..aa6260411f 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -112,6 +112,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CompositeViewEngineWebSite" 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}" @@ -612,6 +614,18 @@ Global {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 @@ -690,6 +704,7 @@ Global {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} EndGlobalSection diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs index ed11373ee0..d5b4377f86 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTestHelper.cs @@ -25,8 +25,10 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { foreach (var input in form.Descendants("input")) { - if (input.Attributes("name").First().Value == "__RequestVerificationToken" && - input.Attributes("type").First().Value == "hidden") + if (input.Attribute("name") != null && + input.Attribute("type") != null && + input.Attribute("name").Value == "__RequestVerificationToken" && + input.Attribute("type").Value == "hidden") { return input.Attributes("value").First().Value; } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Customer.Index.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Customer.Index.html new file mode 100644 index 0000000000..22b7b20f68 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Customer.Index.html @@ -0,0 +1,44 @@ + + + + + + +
+ + + A value is required. +
+
+ + +
+
+ + + +
+
+ + +
+
+ + + The Password field is required. +
+
+ + Male + Female + +
+
  • A value is required.
  • +
  • The Password field is required.
  • +
+
  • +
+ + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Customer.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Customer.html new file mode 100644 index 0000000000..6ba3d0ffc8 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Customer.html @@ -0,0 +1,42 @@ + + + + + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + Male + Female + +
+
  • +
+
+ +
+ + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Index.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Index.html new file mode 100644 index 0000000000..a40ea80fa0 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Index.html @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Order.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Order.html new file mode 100644 index 0000000000..04a01ad1f8 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Order.html @@ -0,0 +1,79 @@ + + + + + + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + Male + Female + +
+
  • +
+ + +
+ + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Product.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Product.html new file mode 100644 index 0000000000..7bffe7572d --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Product.html @@ -0,0 +1,23 @@ + + + + + + + + + +
+
+ + +
+
+ + +
+ +
+ + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs new file mode 100644 index 0000000000..7407bb7887 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs @@ -0,0 +1,92 @@ +// 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.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.TestHost; +using MvcTagHelpersWebSite; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class MvcTagHelpersTests + { + private readonly IServiceProvider _provider = TestHelper.CreateServices("MvcTagHelpersWebSite"); + private readonly Action _app = new Startup().Configure; + private static readonly Assembly _resourcesAssembly = typeof(TagHelpersTests).GetTypeInfo().Assembly; + + [Theory] + [InlineData("Index", null)] + [InlineData("Order", "/MvcTagHelper_Order/Submit")] + [InlineData("Product", null)] + [InlineData("Customer", "/Customer/MvcTagHelper_Customer")] + public async Task MvcTagHelpers_GeneratesExpectedResults(string action, string antiForgeryPath) + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + var expectedMediaType = MediaTypeHeaderValue.Parse("text/html; charset=utf-8"); + + // The K runtime compiles every file under compiler/resources as a resource at runtime with the same name + // as the file name, in order to update a baseline you just need to change the file in that folder. + var expectedContent = + await _resourcesAssembly.ReadResourceAsStringAsync + ("compiler/resources/MvcTagHelpersWebSite.MvcTagHelper_Home." + action + ".html"); + + // Act + // The host is not important as everything runs in memory and tests are isolated from each other. + var response = await client.GetAsync("http://localhost/MvcTagHelper_Home/" + action); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(expectedMediaType, response.Content.Headers.ContentType); + + if (antiForgeryPath != null) + { + var forgeryToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken(responseContent, antiForgeryPath); + expectedContent = string.Format(expectedContent, forgeryToken); + } + Assert.Equal(expectedContent.Trim(), responseContent.Trim()); + } + + [Fact] + public async Task ValidationTagHelpers_GeneratesExpectedSpansAndDivs() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + var expectedContent = + await _resourcesAssembly.ReadResourceAsStringAsync + ("compiler/resources/MvcTagHelpersWebSite.MvcTagHelper_Customer.Index.html"); + + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Customer/MvcTagHelper_Customer"); + var nameValueCollection = new List> + { + new KeyValuePair("Number", string.Empty), + new KeyValuePair("Name", string.Empty), + new KeyValuePair("Email", string.Empty), + new KeyValuePair("PhoneNumber", string.Empty), + new KeyValuePair("Password", string.Empty) + }; + request.Content = new FormUrlEncodedContent(nameValueCollection); + + // Act + var response = await client.SendAsync(request); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var forgeryToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken(responseContent, "Customer/MvcTagHelper_Customer"); + expectedContent = string.Format(expectedContent, forgeryToken); + Assert.Equal(expectedContent.Trim(), responseContent.Trim()); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json index 6b889106c6..a9669ca8df 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/project.json @@ -35,6 +35,7 @@ "WebApiCompatShimWebSite": "1.0.0", "Microsoft.AspNet.TestHost": "1.0.0-*", "Microsoft.AspNet.Mvc.TestConfiguration": "1.0.0", + "MvcTagHelpersWebSite": "1.0.0", "Microsoft.Framework.ConfigurationModel.Json": "1.0.0-*", "xunit.runner.kre": "1.0.0-*", "Microsoft.AspNet.WebUtilities": "1.0.0-*" diff --git a/test/WebSites/MvcTagHelpersWebSite/Areas/Customer/Controllers/MvcTagHelper_CustomerController.cs b/test/WebSites/MvcTagHelpersWebSite/Areas/Customer/Controllers/MvcTagHelper_CustomerController.cs new file mode 100644 index 0000000000..f4c23ee962 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Areas/Customer/Controllers/MvcTagHelper_CustomerController.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; +using Microsoft.AspNet.Mvc; + +namespace MvcTagHelpersWebSite.Areas.Customer.Controllers +{ + [Area("Customer")] + public class MvcTagHelper_CustomerController : Controller + { + public IActionResult Index(MvcTagHelpersWebSite.Models.Customer customer) + { + return View("Customer"); + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs b/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs new file mode 100644 index 0000000000..bd0dad40de --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs @@ -0,0 +1,74 @@ +// 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.AspNet.Mvc; +using Microsoft.AspNet.Mvc.Rendering; +using MvcTagHelpersWebSite.Models; + +namespace MvcTagHelpersWebSite.Controllers +{ + public class MvcTagHelper_HomeController : Controller + { + public IActionResult Order() + { + var products = new List(); + products = new List(); + + for (int i = 7; i < 13; ++i) + { + products.Add(new Product() + { + ProductName = "Product_" + i, + Number = i, + PartNumbers = Enumerable.Range(1, 3).Select(n => string.Format("{0}-{1}", i, n)) + }); + } + + ViewBag.Items = new SelectList(products, "Number", "ProductName", 9); + + var order = new Order() + { + Shipping = "UPSP", + Customer = new Customer() + { + Key = "KeyA", + Number = 1, + Gender = Gender.Female, + Name = "NameStringValue", + }, + NeedSpecialHandle = true, + PaymentMethod = new List { "Check" } + }; + + return View(order); + } + + public IActionResult Product() + { + var product = new Product() + { + HomePage = new System.Uri("http://www.contoso.com"), + Description = "Type the product description" + }; + return View(product); + } + + public IActionResult ProductSubmit(Product product) + { + throw new NotImplementedException(); + } + + public IActionResult Customer() + { + return View(); + } + + public IActionResult Index() + { + return View(); + } + } +} diff --git a/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_OrderController.cs b/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_OrderController.cs new file mode 100644 index 0000000000..63870408ed --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_OrderController.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; +using Microsoft.AspNet.Mvc; +using MvcTagHelpersWebSite.Models; + +namespace MvcTagHelpersWebSite.Controllers +{ + public class MvcTagHelper_OrderController : Controller + { + public IActionResult Submit(Order order) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_ProductController.cs b/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_ProductController.cs new file mode 100644 index 0000000000..71b891d6ed --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_ProductController.cs @@ -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 System; +using Microsoft.AspNet.Mvc; +using MvcTagHelpersWebSite.Models; + +namespace MvcTagHelpersWebSite.Controllers +{ + public class MvcTagHelper_ProductController : Controller + { + public IActionResult Index() + { + throw new NotImplementedException(); + } + + public IActionResult Submit(Product product) + { + throw new NotImplementedException(); + } + + public IActionResult List() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Models/Customer.cs b/test/WebSites/MvcTagHelpersWebSite/Models/Customer.cs new file mode 100644 index 0000000000..682b2a0c23 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Models/Customer.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.ComponentModel.DataAnnotations; + +namespace MvcTagHelpersWebSite.Models +{ + public class Customer + { + [Range(1, 100)] + public int Number + { + get; + set; + } + + public string Key + { + get; + set; + } + + public string Name + { + get; + set; + } + + [Required] + public string Password + { + get; + set; + } + + [EnumDataType(typeof(Gender))] + public Gender Gender + { + get; + set; + } + + public string PhoneNumber + { + get; + set; + } + + [DataType(DataType.EmailAddress)] + public string Email + { + get; + set; + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Models/Gender.cs b/test/WebSites/MvcTagHelpersWebSite/Models/Gender.cs new file mode 100644 index 0000000000..2ce966e4a1 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Models/Gender.cs @@ -0,0 +1,11 @@ +// 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 MvcTagHelpersWebSite.Models +{ + public enum Gender + { + Male, + Female + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Models/Order.cs b/test/WebSites/MvcTagHelpersWebSite/Models/Order.cs new file mode 100644 index 0000000000..8878234937 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Models/Order.cs @@ -0,0 +1,59 @@ +// 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 MvcTagHelpersWebSite.Models +{ + public class Order + { + public bool NeedSpecialHandle + { + get; + set; + } + + public int OrderNumber + { + get; + set; + } + + public DateTimeOffset OrderDate + { + get; + set; + } + + public ICollection PaymentMethod + { + get; + set; + } + + public DateTime ShippingDateTime + { + get; + set; + } + + public string Shipping + { + get; + set; + } + + public IEnumerable Products + { + get; + set; + } + + public Customer Customer + { + get; + set; + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Models/Product.cs b/test/WebSites/MvcTagHelpersWebSite/Models/Product.cs new file mode 100644 index 0000000000..bdf4db8030 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Models/Product.cs @@ -0,0 +1,43 @@ +// 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.ComponentModel.DataAnnotations; + +namespace MvcTagHelpersWebSite.Models +{ + public class Product + { + [Required] + public string ProductName + { + get; + set; + } + + public int Number + { + get; + set; + } + + public string Description + { + get; + set; + } + + public IEnumerable PartNumbers + { + get; + set; + } + + public Uri HomePage + { + get; + set; + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/MvcTagHelpersWebSite.kproj b/test/WebSites/MvcTagHelpersWebSite/MvcTagHelpersWebSite.kproj new file mode 100644 index 0000000000..b0d0f0b700 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/MvcTagHelpersWebSite.kproj @@ -0,0 +1,29 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 920f8a0e-6f7d-4bbe-84ff-840b89099be6 + ..\..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\..\artifacts\bin\$(MSBuildProjectName)\ + + + MvcTagHelpersWebSite + + + MvcTagHelpersWebSite + + + 2.0 + 49646 + + + + + + + + \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Startup.cs b/test/WebSites/MvcTagHelpersWebSite/Startup.cs new file mode 100644 index 0000000000..067828ecdf --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Startup.cs @@ -0,0 +1,38 @@ +// 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.Builder; +using Microsoft.AspNet.Routing; +using Microsoft.Framework.DependencyInjection; + +namespace MvcTagHelpersWebSite +{ + public class Startup + { + public void Configure(IApplicationBuilder app) + { + var configuration = app.GetTestConfiguration(); + + app.UseServices(services => + { + services.AddMvc(configuration); + }); + + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller}/{action}/{id?}", + defaults: new { controller = "MvcTagHelper_Home", action = "Index" }); + routes.MapRoute( + name: "areaRoute", + template: "{area:exists}/{controller}/{action}/{id?}", + defaults: new { action = "Index" }); + routes.MapRoute( + name: "productRoute", + template: "Product/{action}", + defaults: new { controller = "Product" }); + }); + } + } +} diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Index.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Index.cshtml new file mode 100644 index 0000000000..c6db4ade18 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Index.cshtml @@ -0,0 +1,91 @@ +@{ + ViewBag.Title = "Home Page"; +} + +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers.AnchorTagHelper, Microsoft.AspNet.Mvc.TagHelpers" + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Order.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Order.cshtml new file mode 100644 index 0000000000..cf5c51fd0c --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Order.cshtml @@ -0,0 +1,83 @@ +@model MvcTagHelpersWebSite.Models.Order + +@{ + ViewBag.Title = "Order Page"; +} + +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers.FormTagHelper, Microsoft.AspNet.Mvc.TagHelpers" +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers.InputTagHelper, Microsoft.AspNet.Mvc.TagHelpers" +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers.LabelTagHelper, Microsoft.AspNet.Mvc.TagHelpers" +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers.OptionTagHelper, Microsoft.AspNet.Mvc.TagHelpers" +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers.SelectTagHelper, Microsoft.AspNet.Mvc.TagHelpers" +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers.ValidationMessageTagHelper, Microsoft.AspNet.Mvc.TagHelpers" +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers.ValidationSummaryTagHelper, Microsoft.AspNet.Mvc.TagHelpers" + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + + + \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Product.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Product.cshtml new file mode 100644 index 0000000000..559af73824 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/Product.cshtml @@ -0,0 +1,27 @@ +@model MvcTagHelpersWebSite.Models.Product + +@{ + ViewBag.Title = "Product Page"; +} + +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers" + + + + + + + +
+
+
+
+
+
+ + +
+
+ + +
+
+ + + +
+
+ + Female + Male +
+
+ + + +
+ +
  • +
+ +
+ + diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.EmployeeList.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.EmployeeList.html new file mode 100644 index 0000000000..35b0467617 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.EmployeeList.html @@ -0,0 +1,104 @@ + + + + + +
+
+ + + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ + + +
+
+ + +
+
+ + +
+
+ + + +
+
+ + +
+
+ + + +
+
+ \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Order.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Order.html index 04a01ad1f8..33456a1703 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Order.html +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.Order.html @@ -18,12 +18,9 @@
- + +
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.ProductList.html b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.ProductList.html new file mode 100644 index 0000000000..80913b1e19 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/MvcTagHelpersWebSite.MvcTagHelper_Home.ProductList.html @@ -0,0 +1,60 @@ + + + + + + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs index 7407bb7887..7f39fe9c82 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs @@ -26,6 +26,14 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests [InlineData("Order", "/MvcTagHelper_Order/Submit")] [InlineData("Product", null)] [InlineData("Customer", "/Customer/MvcTagHelper_Customer")] + // Testing InputTagHelpers invoked in the partial views + [InlineData("ProductList", null)] + // Testing MvcTagHelpers invoked in the editor templates with the HTML helpers + [InlineData("EmployeeList", null)] + // Testing SelectTagHelper with Html.BeginForm + [InlineData("CreateWarehouse", null)] + // Testing the HTML helpers with FormTagHelper + [InlineData("EditWarehouse", null)] public async Task MvcTagHelpers_GeneratesExpectedResults(string action, string antiForgeryPath) { // Arrange diff --git a/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs b/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs index bd0dad40de..e40e39a30f 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs +++ b/test/WebSites/MvcTagHelpersWebSite/Controllers/MvcTagHelper_HomeController.cs @@ -12,27 +12,37 @@ namespace MvcTagHelpersWebSite.Controllers { public class MvcTagHelper_HomeController : Controller { + private readonly List _products = new List(); + + public MvcTagHelper_HomeController() + { + _products.Add(new Product + { + ProductName = "Product_0", + Number = 0, + HomePage = new Uri("http://www.contoso.com") + }); + _products.Add(new Product + { + ProductName = "Product_1", + Number = 1, + }); + _products.Add(new Product + { + ProductName = "Product_2", + Number = 2, + Description = "Product_2 desription" + }); + } + public IActionResult Order() { - var products = new List(); - products = new List(); + ViewBag.Items = new SelectList(_products, "Number", "ProductName", 2); - for (int i = 7; i < 13; ++i) - { - products.Add(new Product() - { - ProductName = "Product_" + i, - Number = i, - PartNumbers = Enumerable.Range(1, 3).Select(n => string.Format("{0}-{1}", i, n)) - }); - } - - ViewBag.Items = new SelectList(products, "Number", "ProductName", 9); - - var order = new Order() + var order = new Order { Shipping = "UPSP", - Customer = new Customer() + Customer = new Customer { Key = "KeyA", Number = 1, @@ -48,7 +58,7 @@ namespace MvcTagHelpersWebSite.Controllers public IActionResult Product() { - var product = new Product() + var product = new Product { HomePage = new System.Uri("http://www.contoso.com"), Description = "Type the product description" @@ -70,5 +80,61 @@ namespace MvcTagHelpersWebSite.Controllers { return View(); } + + public IActionResult ProductList() + { + return View(_products); + } + + public IActionResult EmployeeList() + { + var employees = new List(); + + employees.Add(new Employee + { + Name = "EmployeeName_0", + Number = 0, + Address = "Employee_0 address" + }); + employees.Add(new Employee + { + Name = "EmployeeName_1", + Number = 1, + OfficeNumber = "1002", + Gender = Gender.Female + }); + employees.Add(new Employee + { + Name = "EmployeeName_2", + Number = 2, + Remote = true + }); + + return View(employees); + } + + public IActionResult CreateWarehouse() + { + ViewBag.Items = new SelectList(_products, "Number", "ProductName", 9); + + return View(); + } + + public IActionResult EditWarehouse() + { + var warehouse = new Warehouse + { + City = "City_1", + Employee = new Employee + { + Name = "EmployeeName_1", + Number = 1, + Address = "Address_1", + PhoneNumber = "PhoneNumber_1", + Gender = Gender.Female + } + }; + return View(warehouse); + } } } diff --git a/test/WebSites/MvcTagHelpersWebSite/Models/Customer.cs b/test/WebSites/MvcTagHelpersWebSite/Models/Customer.cs index 682b2a0c23..77fc33d17d 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Models/Customer.cs +++ b/test/WebSites/MvcTagHelpersWebSite/Models/Customer.cs @@ -1,56 +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. -using System.ComponentModel.DataAnnotations; - namespace MvcTagHelpersWebSite.Models { - public class Customer + public class Customer : Person { - [Range(1, 100)] - public int Number - { - get; - set; - } - public string Key { get; set; } - - public string Name - { - get; - set; - } - - [Required] - public string Password - { - get; - set; - } - - [EnumDataType(typeof(Gender))] - public Gender Gender - { - get; - set; - } - - public string PhoneNumber - { - get; - set; - } - - [DataType(DataType.EmailAddress)] - public string Email - { - get; - set; - } } } \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Models/Employee.cs b/test/WebSites/MvcTagHelpersWebSite/Models/Employee.cs new file mode 100644 index 0000000000..3c2364e9c4 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Models/Employee.cs @@ -0,0 +1,26 @@ +// 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 MvcTagHelpersWebSite.Models +{ + public class Employee : Person + { + public string Address + { + get; + set; + } + + public string OfficeNumber + { + get; + set; + } + + public bool Remote + { + get; + set; + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Models/Person.cs b/test/WebSites/MvcTagHelpersWebSite/Models/Person.cs new file mode 100644 index 0000000000..1718fe9913 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Models/Person.cs @@ -0,0 +1,50 @@ +// 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.ComponentModel.DataAnnotations; + +namespace MvcTagHelpersWebSite.Models +{ + public class Person + { + [Range(1, 100)] + public int Number + { + get; + set; + } + + public string Name + { + get; + set; + } + + [Required] + public string Password + { + get; + set; + } + + [EnumDataType(typeof(Gender))] + public Gender Gender + { + get; + set; + } + + public string PhoneNumber + { + get; + set; + } + + [DataType(DataType.EmailAddress)] + public string Email + { + get; + set; + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Models/Product.cs b/test/WebSites/MvcTagHelpersWebSite/Models/Product.cs index bdf4db8030..818f4667e5 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Models/Product.cs +++ b/test/WebSites/MvcTagHelpersWebSite/Models/Product.cs @@ -28,12 +28,6 @@ namespace MvcTagHelpersWebSite.Models set; } - public IEnumerable PartNumbers - { - get; - set; - } - public Uri HomePage { get; diff --git a/test/WebSites/MvcTagHelpersWebSite/Models/Warehouse.cs b/test/WebSites/MvcTagHelpersWebSite/Models/Warehouse.cs new file mode 100644 index 0000000000..91171edb78 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Models/Warehouse.cs @@ -0,0 +1,30 @@ +// 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.ComponentModel.DataAnnotations; + +namespace MvcTagHelpersWebSite.Models +{ + public class Warehouse + { + [MinLength(2)] + public string City + { + get; + set; + } + + [Range(1, 100)] + public int Product + { + get; + set; + } + + public Employee Employee + { + get; + set; + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/CreateWarehouse.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/CreateWarehouse.cshtml new file mode 100644 index 0000000000..8e2063b18d --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/MvcTagHelper_Home/CreateWarehouse.cshtml @@ -0,0 +1,27 @@ +@model MvcTagHelpersWebSite.Models.Warehouse + +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers" + +@{ + ViewBag.Title = "Warehouse - Product"; +} + + + + @using (Html.BeginForm("CreateWarehouse", "MvcTagHelper_Home")) + { +
+
+
+
[HtmlElementName("span")] - [ContentBehavior(ContentBehavior.Modify)] public class ValidationMessageTagHelper : TagHelper { private const string ValidationForAttributeName = "asp-validation-for"; @@ -33,7 +32,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// /// Does nothing if is null. - public override void Process(TagHelperContext context, TagHelperOutput output) + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { if (For != null) { @@ -50,9 +49,19 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // We check for whitespace to detect scenarios such as: // // - if (string.IsNullOrWhiteSpace(output.Content)) + if (!output.ContentSet) { - output.Content = tagBuilder.InnerHtml; + var childContent = await context.GetChildContentAsync(); + + if (string.IsNullOrWhiteSpace(childContent)) + { + // Provide default label text since there was nothing useful in the Razor source. + output.Content = tagBuilder.InnerHtml; + } + else + { + output.Content = childContent; + } } } } diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs index 480945d9ff..5ef65fd5ac 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/ValidationSummaryTagHelper.cs @@ -13,7 +13,6 @@ namespace Microsoft.AspNet.Mvc.TagHelpers /// attribute. /// [HtmlElementName("div")] - [ContentBehavior(ContentBehavior.Append)] public class ValidationSummaryTagHelper : TagHelper { private const string ValidationSummaryAttributeName = "asp-validation-summary"; @@ -81,7 +80,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers if (tagBuilder != null) { output.MergeAttributes(tagBuilder); - output.Content += tagBuilder.InnerHtml; + output.PostContent += tagBuilder.InnerHtml; } } } diff --git a/test/WebSites/ActivatorWebSite/TagHelpers/HiddenTagHelper.cs b/test/WebSites/ActivatorWebSite/TagHelpers/HiddenTagHelper.cs index 45efdcca19..f57d7daf0f 100644 --- a/test/WebSites/ActivatorWebSite/TagHelpers/HiddenTagHelper.cs +++ b/test/WebSites/ActivatorWebSite/TagHelpers/HiddenTagHelper.cs @@ -4,12 +4,11 @@ using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor.Runtime.TagHelpers; -using Microsoft.AspNet.Razor.TagHelpers; +using System.Threading.Tasks; namespace ActivatorWebSite.TagHelpers { [HtmlElementName("span")] - [ContentBehavior(ContentBehavior.Modify)] public class HiddenTagHelper : TagHelper { public string Name { get; set; } @@ -17,9 +16,11 @@ namespace ActivatorWebSite.TagHelpers [Activate] public IHtmlHelper HtmlHelper { get; set; } - public override void Process(TagHelperContext context, TagHelperOutput output) + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { - output.Content = HtmlHelper.Hidden(Name, output.Content).ToString(); + var content = await context.GetChildContentAsync(); + + output.Content = HtmlHelper.Hidden(Name, content).ToString(); } } } \ No newline at end of file diff --git a/test/WebSites/ActivatorWebSite/TagHelpers/RepeatContentTagHelper.cs b/test/WebSites/ActivatorWebSite/TagHelpers/RepeatContentTagHelper.cs index f1bc08a8ec..da9d85dce5 100644 --- a/test/WebSites/ActivatorWebSite/TagHelpers/RepeatContentTagHelper.cs +++ b/test/WebSites/ActivatorWebSite/TagHelpers/RepeatContentTagHelper.cs @@ -4,12 +4,11 @@ using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor.Runtime.TagHelpers; -using Microsoft.AspNet.Razor.TagHelpers; +using System.Threading.Tasks; namespace ActivatorWebSite.TagHelpers { [HtmlElementName("div")] - [ContentBehavior(ContentBehavior.Modify)] public class RepeatContentTagHelper : TagHelper { public int RepeatContent { get; set; } @@ -19,14 +18,14 @@ namespace ActivatorWebSite.TagHelpers [Activate] public IHtmlHelper HtmlHelper { get; set; } - public override void Process(TagHelperContext context, TagHelperOutput output) + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { + var content = await context.GetChildContentAsync(); var repeatContent = HtmlHelper.Encode(Expression.Metadata.Model.ToString()); if (string.IsNullOrEmpty(repeatContent)) { - repeatContent = output.Content; - output.Content = string.Empty; + repeatContent = content; } for (int i = 0; i < RepeatContent; i++) diff --git a/test/WebSites/ActivatorWebSite/TagHelpers/TitleTagHelper.cs b/test/WebSites/ActivatorWebSite/TagHelpers/TitleTagHelper.cs index 459a3a99a4..8c71fef5fd 100644 --- a/test/WebSites/ActivatorWebSite/TagHelpers/TitleTagHelper.cs +++ b/test/WebSites/ActivatorWebSite/TagHelpers/TitleTagHelper.cs @@ -9,7 +9,6 @@ using Microsoft.AspNet.Razor.TagHelpers; namespace ActivatorWebSite.TagHelpers { [HtmlElementName("body")] - [ContentBehavior(ContentBehavior.Prepend)] public class TitleTagHelper : TagHelper { [Activate] @@ -23,7 +22,7 @@ namespace ActivatorWebSite.TagHelpers var builder = new TagBuilder("h2"); var title = ViewContext.ViewBag.Title; builder.InnerHtml = HtmlHelper.Encode(title); - output.Content = builder.ToString(); + output.PreContent = builder.ToString(); } } } \ No newline at end of file From eb1eca9e1a8cfb6a2db4849040b1ac9a7f92471b Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Wed, 17 Dec 2014 16:33:07 -0800 Subject: [PATCH 091/118] Modify TagHelper tests to abide by new content mode design. - React to aspnet/Razor#221 - Modified existing TagHelper tests to no longer rely on ContentBehavior. - Updated signatures of TagHelperExecutionContext and TagHelperContext pieces. --- .../Runtime/ModelExpressionTagHelper.cs | 36 ++++- .../AnchorTagHelperTest.cs | 35 ++-- .../FormTagHelperTest.cs | 76 +++++---- .../InputTagHelperTest.cs | 112 ++++++++++--- .../LabelTagHelperTest.cs | 153 +++++++++++++++--- .../OptionTagHelperTest.cs | 32 +++- .../SelectTagHelperTest.cs | 82 +++++++--- .../TagHelperOutputExtensionsTest.cs | 46 +++--- .../TextAreaTagHelperTest.cs | 20 ++- .../ValidationMessageTagHelperTest.cs | 127 ++++++++++++--- .../ValidationSummaryTagHelperTest.cs | 77 +++++++-- .../TagHelpers/HiddenTagHelper.cs | 2 +- .../TagHelpers/RepeatContentTagHelper.cs | 2 +- .../RequestScopedTagHelper.cs | 1 - .../TagHelpers/ATagHelper.cs | 3 +- .../TagHelpers/AutoLinkerTagHelper.cs | 8 +- .../TagHelpers/ConditionTagHelper.cs | 4 +- .../TagHelpers/NestedViewStartTagHelper.cs | 1 - .../TagHelpers/PrettyTagHelper.cs | 5 +- .../TagHelpers/RootViewStartTagHelper.cs | 1 - .../TagCloudViewComponentTagHelper.cs | 1 - .../TagHelpers/WebsiteInformationTagHelper.cs | 4 +- 22 files changed, 627 insertions(+), 201 deletions(-) diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs index c0bfc2df2f..7aadb85538 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/ModelExpressionTagHelper.cs @@ -45,7 +45,9 @@ namespace Asp BeginContext(120, 2, true); WriteLiteral("\r\n"); EndContext(); - __tagHelperExecutionContext = __tagHelperScopeManager.Begin("input-test", "test"); + __tagHelperExecutionContext = __tagHelperScopeManager.Begin("input-test", "test", async() => { + } + , StartWritingScope, EndWritingScope); __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper = CreateTagHelper(); __tagHelperExecutionContext.Add(__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper); #line 5 "TestFiles/Input/ModelExpressionTagHelper.cshtml" @@ -56,12 +58,28 @@ __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For = CreateModelExpression(__mo __tagHelperExecutionContext.AddTagHelperAttribute("for", __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For); __tagHelperExecutionContext.Output = __tagHelperRunner.RunAsync(__tagHelperExecutionContext).Result; WriteLiteral(__tagHelperExecutionContext.Output.GenerateStartTag()); + WriteLiteral(__tagHelperExecutionContext.Output.GeneratePreContent()); + if (__tagHelperExecutionContext.Output.ContentSet) + { + WriteLiteral(__tagHelperExecutionContext.Output.GenerateContent()); + } + else if (__tagHelperExecutionContext.ChildContentRetrieved) + { + WriteLiteral(__tagHelperExecutionContext.GetChildContentAsync().Result); + } + else + { + __tagHelperExecutionContext.ExecuteChildContentAsync().Wait(); + } + WriteLiteral(__tagHelperExecutionContext.Output.GeneratePostContent()); WriteLiteral(__tagHelperExecutionContext.Output.GenerateEndTag()); __tagHelperExecutionContext = __tagHelperScopeManager.End(); BeginContext(146, 2, true); WriteLiteral("\r\n"); EndContext(); - __tagHelperExecutionContext = __tagHelperScopeManager.Begin("input-test", "test"); + __tagHelperExecutionContext = __tagHelperScopeManager.Begin("input-test", "test", async() => { + } + , StartWritingScope, EndWritingScope); __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper = CreateTagHelper(); __tagHelperExecutionContext.Add(__Microsoft_AspNet_Mvc_Razor_InputTestTagHelper); #line 6 "TestFiles/Input/ModelExpressionTagHelper.cshtml" @@ -72,6 +90,20 @@ __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For = CreateModelExpression(__mo __tagHelperExecutionContext.AddTagHelperAttribute("for", __Microsoft_AspNet_Mvc_Razor_InputTestTagHelper.For); __tagHelperExecutionContext.Output = __tagHelperRunner.RunAsync(__tagHelperExecutionContext).Result; WriteLiteral(__tagHelperExecutionContext.Output.GenerateStartTag()); + WriteLiteral(__tagHelperExecutionContext.Output.GeneratePreContent()); + if (__tagHelperExecutionContext.Output.ContentSet) + { + WriteLiteral(__tagHelperExecutionContext.Output.GenerateContent()); + } + else if (__tagHelperExecutionContext.ChildContentRetrieved) + { + WriteLiteral(__tagHelperExecutionContext.GetChildContentAsync().Result); + } + else + { + __tagHelperExecutionContext.ExecuteChildContentAsync().Wait(); + } + WriteLiteral(__tagHelperExecutionContext.Output.GeneratePostContent()); WriteLiteral(__tagHelperExecutionContext.Output.GenerateEndTag()); __tagHelperExecutionContext = __tagHelperScopeManager.End(); } diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs index 56427a00b7..e38d39037d 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs @@ -32,15 +32,18 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "asp-host", "contoso.com" }, { "asp-protocol", "http" } }, - uniqueId: "test"); + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something Else")); var output = new TagHelperOutput( expectedTagName, attributes: new Dictionary { { "id", "myanchor" }, { "asp-route-foo", "bar" }, - }, - content: "Something"); + }) + { + Content = "Something" + }; var urlHelper = new Mock(); urlHelper @@ -85,11 +88,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { // Arrange var context = new TagHelperContext( - allAttributes: new Dictionary(), uniqueId: "test"); + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var output = new TagHelperOutput( "a", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()) + { + Content = string.Empty + }; var generator = new Mock(MockBehavior.Strict); generator @@ -119,11 +126,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { // Arrange var context = new TagHelperContext( - allAttributes: new Dictionary(), uniqueId: "test"); + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var output = new TagHelperOutput( "a", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()) + { + Content = string.Empty + }; var generator = new Mock(); generator @@ -166,8 +177,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers attributes: new Dictionary() { { "href", "http://www.contoso.com" } - }, - content: string.Empty); + }); if (propertyName == "asp-route-") { output.Attributes.Add("asp-route-foo", "bar"); @@ -202,8 +212,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers typeof(AnchorTagHelper).GetProperty(propertyName).SetValue(anchorTagHelper, "Home"); var output = new TagHelperOutput( "a", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()); var expectedErrorMessage = "Cannot determine an 'href' attribute for . An with a specified " + "'asp-route' must not have an 'asp-action' or 'asp-controller' attribute."; diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs index 8494fb6bd3..7fc7153888 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/FormTagHelperTest.cs @@ -33,15 +33,18 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "method", "post" }, { "asp-anti-forgery", true } }, - uniqueId: "test"); + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something Else")); var output = new TagHelperOutput( expectedTagName, attributes: new Dictionary { { "id", "myform" }, { "asp-route-foo", "bar" }, - }, - content: "Something"); + }) + { + PostContent = "Something" + }; var urlHelper = new Mock(); urlHelper .Setup(mock => mock.Action(It.IsAny(), @@ -56,8 +59,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var viewContext = TestableHtmlGenerator.GetViewContext(model: null, htmlGenerator: htmlGenerator, metadataProvider: metadataProvider); - var expectedContent = "Something" + htmlGenerator.GenerateAntiForgery(viewContext) - .ToString(TagRenderMode.SelfClosing); + var expectedPostContent = "Something" + htmlGenerator.GenerateAntiForgery(viewContext) + .ToString(TagRenderMode.SelfClosing); var formTagHelper = new FormTagHelper { Action = "index", @@ -79,7 +82,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal("post", attribute.Value); attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("action")); Assert.Equal("home/index", attribute.Value); - Assert.Equal(expectedContent, output.Content); + Assert.Empty(output.PreContent); + Assert.Empty(output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.Equal(expectedTagName, output.TagName); } @@ -87,16 +92,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers [InlineData(true, "")] [InlineData(false, "")] [InlineData(null, "")] - public async Task ProcessAsync_GeneratesAntiForgeryCorrectly(bool? antiForgery, string expectedContent) + public async Task ProcessAsync_GeneratesAntiForgeryCorrectly(bool? antiForgery, string expectedPostContent) { // Arrange var viewContext = CreateViewContext(); var context = new TagHelperContext( - allAttributes: new Dictionary(), uniqueId: "test"); + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var output = new TagHelperOutput( "form", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()); var generator = new Mock(MockBehavior.Strict); generator .Setup(mock => mock.GenerateForm( @@ -124,7 +130,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Assert Assert.Equal("form", output.TagName); Assert.Empty(output.Attributes); - Assert.Equal(expectedContent, output.Content); + Assert.Empty(output.PreContent); + Assert.Empty(output.Content); + Assert.Equal(expectedPostContent, output.PostContent); } [Fact] @@ -133,7 +141,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var testViewContext = CreateViewContext(); var context = new TagHelperContext( - allAttributes: new Dictionary(), uniqueId: "test"); + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var expectedAttribute = new KeyValuePair("asp-ROUTEE-NotRoute", "something"); var output = new TagHelperOutput( "form", @@ -141,8 +151,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { { "asp-route-val", "hello" }, { "asp-roUte--Foo", "bar" } - }, - content: string.Empty); + }); output.Attributes.Add(expectedAttribute); var generator = new Mock(MockBehavior.Strict); @@ -184,7 +193,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal("form", output.TagName); var attribute = Assert.Single(output.Attributes); Assert.Equal(expectedAttribute, attribute); + Assert.Empty(output.PreContent); Assert.Empty(output.Content); + Assert.Empty(output.PostContent); generator.Verify(); } @@ -194,11 +205,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var viewContext = CreateViewContext(); var context = new TagHelperContext( - allAttributes: new Dictionary(), uniqueId: "test"); + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var output = new TagHelperOutput( "form", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()); var generator = new Mock(MockBehavior.Strict); generator .Setup(mock => mock.GenerateForm(viewContext, "Index", "Home", null, "POST", null)) @@ -220,7 +232,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal("form", output.TagName); Assert.Empty(output.Attributes); + Assert.Empty(output.PreContent); Assert.Empty(output.Content); + Assert.Empty(output.PostContent); } [Theory] @@ -238,14 +252,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers attributes: new Dictionary { { "aCTiON", htmlAction }, - }, - content: string.Empty); + }); + var context = new TagHelperContext( allAttributes: new Dictionary() { { "METhod", "POST" } }, - uniqueId: "test"); + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); // Act await formTagHelper.ProcessAsync(context, output); @@ -257,7 +272,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal(htmlAction, attribute.Value); attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("METhod")); Assert.Equal("POST", attribute.Value); + Assert.Empty(output.PreContent); Assert.Empty(output.Content); + Assert.Empty(output.PostContent); } [Theory] @@ -266,11 +283,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers [InlineData(null, "")] public async Task ProcessAsync_SupportsAntiForgeryIfActionIsSpecified( bool? antiForgery, - string expectedContent) + string expectedPostContent) { // Arrange var viewContext = CreateViewContext(); var generator = new Mock(); + generator.Setup(mock => mock.GenerateAntiForgery(It.IsAny())) .Returns(new TagBuilder("input")); var formTagHelper = new FormTagHelper @@ -284,9 +302,12 @@ namespace Microsoft.AspNet.Mvc.TagHelpers attributes: new Dictionary { { "aCTiON", "my-action" }, - }, - content: string.Empty); - var context = new TagHelperContext(allAttributes: new Dictionary(), uniqueId: "test"); + }); + var context = new TagHelperContext( + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); + // Act await formTagHelper.ProcessAsync(context, output); @@ -295,7 +316,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal("form", output.TagName); var attribute = Assert.Single(output.Attributes); Assert.Equal(new KeyValuePair("aCTiON", "my-action"), attribute); - Assert.Equal(expectedContent, output.Content); + Assert.Empty(output.PreContent); + Assert.Empty(output.Content); + Assert.Equal(expectedPostContent, output.PostContent); } [Theory] @@ -314,8 +337,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers attributes: new Dictionary { { "action", "my-action" }, - }, - content: string.Empty); + }); if (propertyName == "asp-route-") { tagHelperOutput.Attributes.Add("asp-route-foo", "bar"); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs index ac62d6a1e7..adf43c2d35 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/InputTagHelperTest.cs @@ -89,16 +89,23 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "valid", "from validation attributes" }, { "value", expectedValue }, }; + var expectedPreContent = "original pre-content"; var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var expectedTagName = "not-input"; - var context = new TagHelperContext(new Dictionary(), uniqueId: "test"); + var context = new TagHelperContext(allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var originalAttributes = new Dictionary { { "class", "form-control" }, }; - var output = new TagHelperOutput(expectedTagName, originalAttributes, expectedContent) + var output = new TagHelperOutput(expectedTagName, originalAttributes) { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, SelfClosing = false, }; @@ -124,7 +131,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Assert Assert.Equal(expectedAttributes, output.Attributes); + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.True(output.SelfClosing); Assert.Equal(expectedTagName, output.TagName); } @@ -133,17 +142,24 @@ namespace Microsoft.AspNet.Mvc.TagHelpers public async Task ProcessAsync_CallsGenerateCheckBox_WithExpectedParameters() { // Arrange - var originalContent = "something"; + var originalContent = "original content"; var originalTagName = "not-input"; + var expectedPreContent = "original pre-content"; var expectedContent = originalContent + ""; + var expectedPostContent = "original post-content"; - var context = new TagHelperContext(allAttributes: new Dictionary(), uniqueId: "test"); + var context = new TagHelperContext(allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var originalAttributes = new Dictionary { { "class", "form-control" }, }; - var output = new TagHelperOutput(originalTagName, originalAttributes, content: originalContent) + var output = new TagHelperOutput(originalTagName, originalAttributes) { + PreContent = expectedPreContent, + Content = originalContent, + PostContent = expectedPostContent, SelfClosing = true, }; @@ -180,9 +196,11 @@ namespace Microsoft.AspNet.Mvc.TagHelpers htmlGenerator.Verify(); Assert.Empty(output.Attributes); // Moved to Content and cleared + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.False(output.SelfClosing); - Assert.Empty(output.TagName); // Cleared + Assert.Null(output.TagName); // Cleared } [Theory] @@ -214,16 +232,23 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "class", "form-control hidden-control" }, { "type", inputTypeName ?? "hidden" }, // Generator restores type attribute; adds "hidden" if none. }; - var expectedContent = "something"; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var expectedTagName = "not-input"; - var context = new TagHelperContext(allAttributes: contextAttributes, uniqueId: "test"); + var context = new TagHelperContext(allAttributes: contextAttributes, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var originalAttributes = new Dictionary { { "class", "form-control" }, }; - var output = new TagHelperOutput(expectedTagName, originalAttributes, content: expectedContent) + var output = new TagHelperOutput(expectedTagName, originalAttributes) { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, SelfClosing = false, }; @@ -257,7 +282,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers htmlGenerator.Verify(); Assert.Equal(expectedAttributes, output.Attributes); + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.True(output.SelfClosing); Assert.Equal(expectedTagName, output.TagName); } @@ -291,16 +318,23 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "class", "form-control password-control" }, { "type", inputTypeName ?? "password" }, // Generator restores type attribute; adds "password" if none. }; - var expectedContent = "something"; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var expectedTagName = "not-input"; - var context = new TagHelperContext(allAttributes: contextAttributes, uniqueId: "test"); + var context = new TagHelperContext(allAttributes: contextAttributes, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var originalAttributes = new Dictionary { { "class", "form-control" }, }; - var output = new TagHelperOutput(expectedTagName, originalAttributes, content: expectedContent) + var output = new TagHelperOutput(expectedTagName, originalAttributes) { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, SelfClosing = false, }; @@ -333,7 +367,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers htmlGenerator.Verify(); Assert.Equal(expectedAttributes, output.Attributes); + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.True(output.SelfClosing); Assert.Equal(expectedTagName, output.TagName); } @@ -365,16 +401,23 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "type", inputTypeName ?? "radio" }, // Generator restores type attribute; adds "radio" if none. { "value", value }, }; - var expectedContent = "something"; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var expectedTagName = "not-input"; - var context = new TagHelperContext(allAttributes: contextAttributes, uniqueId: "test"); + var context = new TagHelperContext(allAttributes: contextAttributes, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var originalAttributes = new Dictionary { { "class", "form-control" }, }; - var output = new TagHelperOutput(expectedTagName, originalAttributes, content: expectedContent) + var output = new TagHelperOutput(expectedTagName, originalAttributes) { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, SelfClosing = false, }; @@ -408,7 +451,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers htmlGenerator.Verify(); Assert.Equal(expectedAttributes, output.Attributes); + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.True(output.SelfClosing); Assert.Equal(expectedTagName, output.TagName); } @@ -454,16 +499,23 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "class", "form-control text-control" }, { "type", inputTypeName ?? "text" }, // Generator restores type attribute; adds "text" if none. }; - var expectedContent = "something"; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var expectedTagName = "not-input"; - var context = new TagHelperContext(allAttributes: contextAttributes, uniqueId: "test"); + var context = new TagHelperContext(allAttributes: contextAttributes, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var originalAttributes = new Dictionary { { "class", "form-control" }, }; - var output = new TagHelperOutput(expectedTagName, originalAttributes, content: expectedContent) + var output = new TagHelperOutput(expectedTagName, originalAttributes) { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, SelfClosing = false, }; @@ -497,7 +549,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers htmlGenerator.Verify(); Assert.Equal(expectedAttributes, output.Attributes); + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.True(output.SelfClosing); Assert.Equal(expectedTagName, output.TagName); } @@ -512,12 +566,20 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "type", "datetime" }, { "value", "2014-10-15T23:24:19.000-7:00" }, }; + var expectedPreContent = "original pre-content"; var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var expectedTagName = "input"; - var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); - var output = new TagHelperOutput(expectedTagName, expectedAttributes, expectedContent) + var tagHelperContext = new TagHelperContext( + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); + var output = new TagHelperOutput(expectedTagName, expectedAttributes) { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, SelfClosing = false, }; @@ -536,7 +598,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Assert Assert.Equal(expectedAttributes, output.Attributes); + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.False(output.SelfClosing); Assert.Equal(expectedTagName, output.TagName); } @@ -558,13 +622,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var contextAttributes = new Dictionary(); var originalAttributes = new Dictionary(); - var content = "original content"; var expectedTagName = "select"; var expectedMessage = "Unable to format without a 'asp-for' expression for . 'asp-format' must " + "be null if 'asp-for' is null."; - var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); - var output = new TagHelperOutput(expectedTagName, originalAttributes, content); + var tagHelperContext = new TagHelperContext( + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); + var output = new TagHelperOutput(expectedTagName, originalAttributes); var tagHelper = new InputTagHelper { Format = "{0}", diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs index ebce192391..b3b0accf7f 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/LabelTagHelperTest.cs @@ -44,45 +44,111 @@ namespace Microsoft.AspNet.Mvc.TagHelpers return new TheoryData, string, TagHelperOutputContent> { { null, typeof(Model), () => null, "Text", - new TagHelperOutputContent(Environment.NewLine, "Text", "Text") }, + new TagHelperOutputContent(string.Empty, Environment.NewLine, Environment.NewLine, "Text") }, + { null, typeof(Model), () => null, "Text", + new TagHelperOutputContent(Environment.NewLine, string.Empty, "Text", "Text") }, { modelWithNull, typeof(Model), () => modelWithNull.Text, "Text", - new TagHelperOutputContent(Environment.NewLine, "Text", "Text") }, + new TagHelperOutputContent(string.Empty, Environment.NewLine, Environment.NewLine, "Text") }, + { modelWithNull, typeof(Model), () => modelWithNull.Text, "Text", + new TagHelperOutputContent(Environment.NewLine, string.Empty, "Text", "Text") }, + { modelWithNull, typeof(Model), () => modelWithNull.Text, "Text", + new TagHelperOutputContent(Environment.NewLine, "Hello World", "Hello World", "Text") }, { modelWithText, typeof(Model), () => modelWithText.Text, "Text", - new TagHelperOutputContent(Environment.NewLine, "Text", "Text") }, + new TagHelperOutputContent(string.Empty, Environment.NewLine, Environment.NewLine, "Text") }, + { modelWithText, typeof(Model), () => modelWithText.Text, "Text", + new TagHelperOutputContent(Environment.NewLine, string.Empty, "Text", "Text") }, + { modelWithText, typeof(Model), () => modelWithText.Text, "Text", + new TagHelperOutputContent(Environment.NewLine, "Hello World", "Hello World", "Text") }, { modelWithText, typeof(Model), () => modelWithNull.Text, "Text", - new TagHelperOutputContent("Hello World", "Hello World", "Text") }, + new TagHelperOutputContent(string.Empty, "Hello World", "Hello World", "Text") }, { modelWithText, typeof(Model), () => modelWithText.Text, "Text", - new TagHelperOutputContent("Hello World", "Hello World", "Text") }, + new TagHelperOutputContent(string.Empty, "Hello World", "Hello World", "Text") }, + { modelWithText, typeof(Model), () => modelWithNull.Text, "Text", + new TagHelperOutputContent("Hello World", string.Empty, "Hello World", "Text") }, + { modelWithText, typeof(Model), () => modelWithText.Text, "Text", + new TagHelperOutputContent("Hello World", string.Empty, "Hello World", "Text") }, + { modelWithText, typeof(Model), () => modelWithNull.Text, "Text", + new TagHelperOutputContent("Hello World1", "Hello World2", "Hello World2", "Text") }, + { modelWithText, typeof(Model), () => modelWithText.Text, "Text", + new TagHelperOutputContent("Hello World1", "Hello World2", "Hello World2", "Text") }, { modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text", - new TagHelperOutputContent(Environment.NewLine, "Text", "NestedModel_Text") }, - { modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text", - new TagHelperOutputContent(Environment.NewLine, "Text", "NestedModel_Text") }, + new TagHelperOutputContent(Environment.NewLine, string.Empty, "Text", "NestedModel_Text") }, { modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text", - new TagHelperOutputContent("Hello World", "Hello World", "NestedModel_Text") }, + new TagHelperOutputContent(Environment.NewLine, "Hello World", "Hello World", "NestedModel_Text") }, + { modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text", + new TagHelperOutputContent(string.Empty, Environment.NewLine, Environment.NewLine, "NestedModel_Text") }, { modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text", - new TagHelperOutputContent("Hello World", "Hello World", "NestedModel_Text") }, + new TagHelperOutputContent(Environment.NewLine, string.Empty, "Text", "NestedModel_Text") }, + { modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text", + new TagHelperOutputContent(Environment.NewLine, "Hello World", "Hello World", "NestedModel_Text") }, + { modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text", + new TagHelperOutputContent(string.Empty, Environment.NewLine, Environment.NewLine, "NestedModel_Text") }, + { modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text", + new TagHelperOutputContent(string.Empty, "Hello World", "Hello World", "NestedModel_Text") }, + { modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text", + new TagHelperOutputContent(string.Empty, "Hello World", "Hello World", "NestedModel_Text") }, + { modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text", + new TagHelperOutputContent("Hello World", string.Empty, "Hello World", "NestedModel_Text") }, + { modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text", + new TagHelperOutputContent("Hello World", string.Empty, "Hello World", "NestedModel_Text") }, + { modelWithNull, typeof(NestedModel), () => modelWithNull.NestedModel.Text, "NestedModel.Text", + new TagHelperOutputContent("Hello World1", "Hello World2", "Hello World2", "NestedModel_Text") }, + { modelWithText, typeof(NestedModel), () => modelWithText.NestedModel.Text, "NestedModel.Text", + new TagHelperOutputContent("Hello World1", "Hello World2", "Hello World2", "NestedModel_Text") }, // Note: Tests cases below here will not work in practice due to current limitations on indexing // into ModelExpressions. Will be fixed in https://github.com/aspnet/Mvc/issues/1345. { models, typeof(Model), () => models[0].Text, "[0].Text", - new TagHelperOutputContent(Environment.NewLine, "Text", "z0__Text") }, - { models, typeof(Model), () => models[1].Text, "[1].Text", - new TagHelperOutputContent(Environment.NewLine, "Text", "z1__Text") }, + new TagHelperOutputContent(Environment.NewLine, string.Empty, "Text", "z0__Text") }, { models, typeof(Model), () => models[0].Text, "[0].Text", - new TagHelperOutputContent("Hello World", "Hello World", "z0__Text") }, + new TagHelperOutputContent(Environment.NewLine, "Hello World", "Hello World", "z0__Text") }, + { models, typeof(Model), () => models[0].Text, "[0].Text", + new TagHelperOutputContent(string.Empty, Environment.NewLine, Environment.NewLine, "z0__Text") }, { models, typeof(Model), () => models[1].Text, "[1].Text", - new TagHelperOutputContent("Hello World", "Hello World", "z1__Text") }, + new TagHelperOutputContent(Environment.NewLine, string.Empty, "Text", "z1__Text") }, + { models, typeof(Model), () => models[1].Text, "[1].Text", + new TagHelperOutputContent(Environment.NewLine, "Hello World", "Hello World", "z1__Text") }, + { models, typeof(Model), () => models[1].Text, "[1].Text", + new TagHelperOutputContent(string.Empty, Environment.NewLine, Environment.NewLine, "z1__Text") }, + { models, typeof(Model), () => models[0].Text, "[0].Text", + new TagHelperOutputContent(string.Empty, "Hello World", "Hello World", "z0__Text") }, + { models, typeof(Model), () => models[1].Text, "[1].Text", + new TagHelperOutputContent(string.Empty, "Hello World", "Hello World", "z1__Text") }, + { models, typeof(Model), () => models[0].Text, "[0].Text", + new TagHelperOutputContent("Hello World", string.Empty, "Hello World", "z0__Text") }, + { models, typeof(Model), () => models[1].Text, "[1].Text", + new TagHelperOutputContent("Hello World", string.Empty, "Hello World", "z1__Text") }, + { models, typeof(Model), () => models[0].Text, "[0].Text", + new TagHelperOutputContent("Hello World1", "Hello World2", "Hello World2", "z0__Text") }, + { models, typeof(Model), () => models[1].Text, "[1].Text", + new TagHelperOutputContent("Hello World1", "Hello World2", "Hello World2", "z1__Text") }, { models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text", - new TagHelperOutputContent(Environment.NewLine, "Text", "z0__NestedModel_Text") }, - { models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text", - new TagHelperOutputContent(Environment.NewLine, "Text", "z1__NestedModel_Text") }, + new TagHelperOutputContent(Environment.NewLine, string.Empty, "Text", "z0__NestedModel_Text") }, { models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text", - new TagHelperOutputContent("Hello World", "Hello World", "z0__NestedModel_Text") }, + new TagHelperOutputContent(Environment.NewLine, "Hello World", "Hello World", "z0__NestedModel_Text") }, + { models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text", + new TagHelperOutputContent(string.Empty, Environment.NewLine, Environment.NewLine, "z0__NestedModel_Text") }, { models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text", - new TagHelperOutputContent("Hello World", "Hello World", "z1__NestedModel_Text") }, + new TagHelperOutputContent(Environment.NewLine, string.Empty, "Text", "z1__NestedModel_Text") }, + { models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text", + new TagHelperOutputContent(Environment.NewLine, "Hello World", "Hello World", "z1__NestedModel_Text") }, + { models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text", + new TagHelperOutputContent(string.Empty, Environment.NewLine, Environment.NewLine, "z1__NestedModel_Text") }, + { models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text", + new TagHelperOutputContent(string.Empty, "Hello World", "Hello World", "z0__NestedModel_Text") }, + { models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text", + new TagHelperOutputContent(string.Empty, "Hello World", "Hello World", "z1__NestedModel_Text") }, + { models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text", + new TagHelperOutputContent("Hello World", string.Empty, "Hello World", "z0__NestedModel_Text") }, + { models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text", + new TagHelperOutputContent("Hello World", string.Empty, "Hello World", "z1__NestedModel_Text") }, + { models, typeof(NestedModel), () => models[0].NestedModel.Text, "[0].NestedModel.Text", + new TagHelperOutputContent("Hello World1", "Hello World2", "Hello World2", "z0__NestedModel_Text") }, + { models, typeof(NestedModel), () => models[1].NestedModel.Text, "[1].NestedModel.Text", + new TagHelperOutputContent("Hello World1", "Hello World2", "Hello World2", "z1__NestedModel_Text") }, }; } } @@ -112,13 +178,30 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { For = modelExpression, }; + var expectedPreContent = "original pre-content"; + var expectedPostContent = "original post-content"; - var tagHelperContext = new TagHelperContext(allAttributes: new Dictionary(), uniqueId: "test"); + var tagHelperContext = new TagHelperContext( + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult(tagHelperOutputContent.OriginalChildContent)); var htmlAttributes = new Dictionary { { "class", "form-control" }, }; - var output = new TagHelperOutput(expectedTagName, htmlAttributes, tagHelperOutputContent.OriginalContent); + var output = new TagHelperOutput(expectedTagName, htmlAttributes) + { + PreContent = expectedPreContent, + PostContent = expectedPostContent, + }; + + // LabelTagHelper checks ContentSet so we don't want to forcibly set it if + // tagHelperOutputContent.OriginalContent is going to be null or empty. + if (!string.IsNullOrEmpty(tagHelperOutputContent.OriginalContent)) + { + output.Content = tagHelperOutputContent.OriginalContent; + } + var htmlGenerator = new TestableHtmlGenerator(metadataProvider); var viewContext = TestableHtmlGenerator.GetViewContext(model, htmlGenerator, metadataProvider); tagHelper.ViewContext = viewContext; @@ -129,7 +212,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Assert Assert.Equal(expectedAttributes, output.Attributes); + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(tagHelperOutputContent.ExpectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.False(output.SelfClosing); Assert.Equal(expectedTagName, output.TagName); } @@ -142,7 +227,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { { "class", "form-control" }, }; + var expectedPreContent = "original pre-content"; var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var expectedTagName = "label"; var metadataProvider = new DataAnnotationsModelMetadataProvider(); @@ -153,8 +240,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var modelExpression = new ModelExpression(nameof(Model.Text), metadata); var tagHelper = new LabelTagHelper(); - var tagHelperContext = new TagHelperContext(allAttributes: new Dictionary(), uniqueId: "test"); - var output = new TagHelperOutput(expectedTagName, expectedAttributes, expectedContent); + var tagHelperContext = new TagHelperContext( + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); + var output = new TagHelperOutput(expectedTagName, expectedAttributes) + { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, + }; var htmlGenerator = new TestableHtmlGenerator(metadataProvider); Model model = null; @@ -167,19 +262,27 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Assert Assert.Equal(expectedAttributes, output.Attributes); + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.Equal(expectedTagName, output.TagName); } public class TagHelperOutputContent { - public TagHelperOutputContent(string outputContent, string expectedContent, string expectedId) + public TagHelperOutputContent(string originalChildContent, + string outputContent, + string expectedContent, + string expectedId) { + OriginalChildContent = originalChildContent; OriginalContent = outputContent; ExpectedContent = expectedContent; ExpectedId = expectedId; } + public string OriginalChildContent { get; set; } + public string OriginalContent { get; set; } public string ExpectedContent { get; set; } diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs index 24ceea9704..d5089f4024 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/OptionTagHelperTest.cs @@ -136,9 +136,13 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "selected", selected }, { "value", value }, }; - var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); - var output = new TagHelperOutput(expectedTagName, originalAttributes, originalContent) + var tagHelperContext = new TagHelperContext( + contextAttributes, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult(originalContent)); + var output = new TagHelperOutput(expectedTagName, originalAttributes) { + Content = originalContent, SelfClosing = false, }; @@ -188,9 +192,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "selected", selected }, { "value", value }, }; - var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); - var output = new TagHelperOutput(originalTagName, originalAttributes, originalContent) + var originalPreContent = "original pre-content"; + var originalPostContent = "original post-content"; + var tagHelperContext = new TagHelperContext( + contextAttributes, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult(originalContent)); + var output = new TagHelperOutput(originalTagName, originalAttributes) { + PreContent = originalPreContent, + Content = originalContent, + PostContent = originalPostContent, SelfClosing = false, }; @@ -235,9 +247,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "selected", selected }, { "value", value }, }; - var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); - var output = new TagHelperOutput(originalTagName, originalAttributes, originalContent) + var originalPreContent = "original pre-content"; + var originalPostContent = "original post-content"; + var tagHelperContext = new TagHelperContext( + contextAttributes, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult(originalContent)); + var output = new TagHelperOutput(originalTagName, originalAttributes) { + PreContent = originalPreContent, + Content = originalContent, + PostContent = originalPostContent, SelfClosing = false, }; diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs index 07b90efa4e..ff8036dbd0 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/SelectTagHelperTest.cs @@ -165,7 +165,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { { "class", "form-control" }, }; - var originalContent = "original content"; + var originalPostContent = "original content"; var expectedAttributes = new Dictionary(originalAttributes) { @@ -173,7 +173,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "name", nameAndId.Name }, { "valid", "from validation attributes" }, }; - var expectedContent = originalContent; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var expectedPostContent = originalPostContent; var expectedTagName = "not-select"; var metadataProvider = new DataAnnotationsModelMetadataProvider(); @@ -182,9 +184,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var metadata = metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName: "Text"); var modelExpression = new ModelExpression(nameAndId.Name, metadata); - var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); - var output = new TagHelperOutput(expectedTagName, originalAttributes, expectedContent) + var tagHelperContext = new TagHelperContext( + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); + var output = new TagHelperOutput(expectedTagName, originalAttributes) { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = originalPostContent, SelfClosing = true, }; @@ -208,7 +216,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Assert Assert.Equal(expectedAttributes, output.Attributes); + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.False(output.SelfClosing); Assert.Equal(expectedTagName, output.TagName); @@ -235,7 +245,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { { "class", "form-control" }, }; - var originalContent = "original content"; + var originalPostContent = "original content"; var expectedAttributes = new Dictionary(originalAttributes) { @@ -243,7 +253,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "name", nameAndId.Name }, { "valid", "from validation attributes" }, }; - var expectedContent = originalContent + expectedOptions; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var expectedPostContent = originalPostContent + expectedOptions; var expectedTagName = "select"; var metadataProvider = new DataAnnotationsModelMetadataProvider(); @@ -252,9 +264,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var metadata = metadataProvider.GetMetadataForProperty(modelAccessor, containerType, propertyName: "Text"); var modelExpression = new ModelExpression(nameAndId.Name, metadata); - var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); - var output = new TagHelperOutput(expectedTagName, originalAttributes, originalContent) + var tagHelperContext = new TagHelperContext( + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); + var output = new TagHelperOutput(expectedTagName, originalAttributes) { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = originalPostContent, SelfClosing = true, }; @@ -280,7 +298,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Assert Assert.Equal(expectedAttributes, output.Attributes); + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.False(output.SelfClosing); Assert.Equal(expectedTagName, output.TagName); @@ -308,12 +328,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { "multiple", multiple }, }; var originalAttributes = new Dictionary(); - var content = "original content"; var propertyName = "Property1"; var expectedTagName = "select"; - var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); - var output = new TagHelperOutput(expectedTagName, originalAttributes, content); + var tagHelperContext = new TagHelperContext( + contextAttributes, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); + var output = new TagHelperOutput(expectedTagName, originalAttributes); var metadataProvider = new EmptyModelMetadataProvider(); string model = null; @@ -372,12 +394,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var contextAttributes = new Dictionary(); var originalAttributes = new Dictionary(); - var content = "original content"; var propertyName = "Property1"; var tagName = "select"; - var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); - var output = new TagHelperOutput(tagName, originalAttributes, content); + var tagHelperContext = new TagHelperContext( + contextAttributes, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); + var output = new TagHelperOutput(tagName, originalAttributes); var metadataProvider = new EmptyModelMetadataProvider(); var metadata = metadataProvider.GetMetadataForType(() => model, modelType); @@ -437,12 +461,20 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; var expectedAttributes = new Dictionary(originalAttributes); expectedAttributes[attributeName] = (string)contextAttributes[attributeName]; + var expectedPreContent = "original pre-content"; var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var expectedTagName = "select"; - var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); - var output = new TagHelperOutput(expectedTagName, originalAttributes, expectedContent) + var tagHelperContext = new TagHelperContext( + contextAttributes, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); + var output = new TagHelperOutput(expectedTagName, originalAttributes) { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, SelfClosing = true, }; @@ -456,7 +488,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Assert Assert.Equal(expectedAttributes, output.Attributes); + Assert.Equal(expectedPreContent, output.PreContent); Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.True(output.SelfClosing); Assert.Equal(expectedTagName, output.TagName); } @@ -467,12 +501,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var contextAttributes = new Dictionary(); var originalAttributes = new Dictionary(); - var content = "original content"; var expectedTagName = "select"; var expectedMessage = "Cannot determine body for . Acceptable values are 'false', 'true' and 'multiple'."; - var tagHelperContext = new TagHelperContext(contextAttributes, uniqueId: "test"); - var output = new TagHelperOutput(expectedTagName, originalAttributes, content); + var tagHelperContext = new TagHelperContext( + contextAttributes, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); + var output = new TagHelperOutput(expectedTagName, originalAttributes); var metadataProvider = new EmptyModelMetadataProvider(); string model = null; diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs index ba0a980c95..cc7ad9fa4b 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Xunit; @@ -19,14 +20,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var tagHelperOutput = new TagHelperOutput( "p", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()); var tagHelperContext = new TagHelperContext( - new Dictionary(StringComparer.Ordinal) + allAttributes: new Dictionary(StringComparer.Ordinal) { { attributeName, attributeValue } }, - uniqueId: "test"); + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var expectedAttribute = new KeyValuePair(attributeName, attributeValue); // Act @@ -47,15 +48,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers attributes: new Dictionary() { { attributeName, "world2" } - }, - content: string.Empty); + }); var expectedAttribute = new KeyValuePair(attributeName, "world2"); var tagHelperContext = new TagHelperContext( - new Dictionary(StringComparer.Ordinal) + allAttributes: new Dictionary(StringComparer.Ordinal) { { attributeName, "world" } - } - , uniqueId: "test"); + }, + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); // Act tagHelperOutput.CopyHtmlAttribute(attributeName, tagHelperContext); @@ -75,8 +76,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { { "route-Hello", "World" }, { "Route-I", "Am" } - }, - content: string.Empty); + }); var expectedAttribute = new KeyValuePair("type", "btn"); tagHelperOutput.Attributes.Add(expectedAttribute); var attributes = tagHelperOutput.FindPrefixedAttributes("route-"); @@ -99,8 +99,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { { "routeHello", "World" }, { "Routee-I", "Am" } - }, - content: string.Empty); + }); // Act var attributes = tagHelperOutput.FindPrefixedAttributes("route-"); @@ -119,8 +118,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var tagHelperOutput = new TagHelperOutput( "p", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()); var expectedAttribute = new KeyValuePair("type", "btn"); tagHelperOutput.Attributes.Add(expectedAttribute); @@ -141,8 +139,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var tagHelperOutput = new TagHelperOutput( "p", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()); tagHelperOutput.Attributes.Add("class", "Hello"); var tagBuilder = new TagBuilder("p"); @@ -168,8 +165,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var tagHelperOutput = new TagHelperOutput( "p", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()); tagHelperOutput.Attributes.Add(originalName, "Hello"); var tagBuilder = new TagBuilder("p"); @@ -189,8 +185,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var tagHelperOutput = new TagHelperOutput( "p", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()); var tagBuilder = new TagBuilder("p"); var expectedAttribute = new KeyValuePair("visible", "val < 3"); @@ -210,8 +205,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var tagHelperOutput = new TagHelperOutput( "p", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()); var tagBuilder = new TagBuilder("p"); var expectedAttribute1 = new KeyValuePair("class", "btn"); @@ -236,8 +230,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var tagHelperOutput = new TagHelperOutput( "p", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()); var expectedAttribute = new KeyValuePair("class", "btn"); tagHelperOutput.Attributes.Add(expectedAttribute); @@ -257,8 +250,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var tagHelperOutput = new TagHelperOutput( "p", - attributes: new Dictionary(), - content: string.Empty); + attributes: new Dictionary()); var expectedOutputAttribute = new KeyValuePair("class", "btn"); tagHelperOutput.Attributes.Add(expectedOutputAttribute); diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs index 1b098e9e04..5d3ae03516 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs @@ -107,13 +107,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers For = modelExpression, }; - var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); + var tagHelperContext = new TagHelperContext( + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var htmlAttributes = new Dictionary { { "class", "form-control" }, }; - var output = new TagHelperOutput(expectedTagName, htmlAttributes, "original content") + var output = new TagHelperOutput(expectedTagName, htmlAttributes) { + Content = "original content", SelfClosing = true, }; @@ -146,7 +150,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { { "class", "form-control" }, }; + var expectedPreContent = "original pre-content"; var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var expectedTagName = "textarea"; var metadataProvider = new DataAnnotationsModelMetadataProvider(); @@ -157,9 +163,15 @@ namespace Microsoft.AspNet.Mvc.TagHelpers var modelExpression = new ModelExpression(nameof(Model.Text), metadata); var tagHelper = new TextAreaTagHelper(); - var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); - var output = new TagHelperOutput(expectedTagName, expectedAttributes, expectedContent) + var tagHelperContext = new TagHelperContext( + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); + var output = new TagHelperOutput(expectedTagName, expectedAttributes) { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, SelfClosing = true, }; diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs index 44e4f6beef..29bfa1b599 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs @@ -28,20 +28,29 @@ namespace Microsoft.AspNet.Mvc.TagHelpers For = modelExpression }; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var expectedPostContent = "original post-content"; + var tagHelperContext = new TagHelperContext( allAttributes: new Dictionary { { "id", "myvalidationmessage" }, { "for", modelExpression }, }, - uniqueId: "test"); + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var output = new TagHelperOutput( expectedTagName, attributes: new Dictionary { { "id", "myvalidationmessage" } - }, - content: "Something"); + }) + { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, + }; var htmlGenerator = new TestableHtmlGenerator(metadataProvider); var viewContext = TestableHtmlGenerator.GetViewContext(model: null, htmlGenerator: htmlGenerator, @@ -62,7 +71,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal("Name", attribute.Value); attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("data-valmsg-replace")); Assert.Equal("true", attribute.Value); - Assert.Equal("Something", output.Content); + Assert.Equal(expectedPreContent, output.PreContent); + Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); Assert.Equal(expectedTagName, output.TagName); } @@ -74,10 +85,21 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { For = CreateModelExpression("Hello") }; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var expectedPostContent = "original post-content"; + var context = new TagHelperContext( + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var output = new TagHelperOutput( "span", - attributes: new Dictionary(), - content: "Content of validation message"); + attributes: new Dictionary()) + { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, + }; var expectedViewContext = CreateViewContext(); var generator = new Mock(); generator @@ -89,19 +111,22 @@ namespace Microsoft.AspNet.Mvc.TagHelpers validationMessageTagHelper.ViewContext = expectedViewContext; // Act & Assert - await validationMessageTagHelper.ProcessAsync(context: null, output: output); + await validationMessageTagHelper.ProcessAsync(context, output: output); generator.Verify(); Assert.Equal("span", output.TagName); Assert.Empty(output.Attributes); - Assert.Equal("Content of validation message", output.Content); + Assert.Equal(expectedPreContent, output.PreContent); + Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); } [Theory] - [InlineData("Content of validation message", "Content of validation message")] - [InlineData("\r\n \r\n", "New HTML")] - public async Task ProcessAsync_MergesTagBuilderFromGenerateValidationMessage( - string outputContent, string expectedOutputContent) + [InlineData("Content of validation message", "Some Content", "Some Content")] + [InlineData("\r\n \r\n", "\r\n Something Else \r\n", "\r\n Something Else \r\n")] + [InlineData("\r\n \r\n", "Some Content", "Some Content")] + public async Task ProcessAsync_DoesNotOverrideOutputContent( + string childContent, string outputContent, string expectedOutputContent) { // Arrange var validationMessageTagHelper = new ValidationMessageTagHelper @@ -110,8 +135,14 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; var output = new TagHelperOutput( "span", - attributes: new Dictionary(), - content: outputContent); + attributes: new Dictionary()) + { + Content = outputContent + }; + + var context = new TagHelperContext(allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult(childContent)); var tagBuilder = new TagBuilder("span2") { InnerHtml = "New HTML" @@ -133,7 +164,58 @@ namespace Microsoft.AspNet.Mvc.TagHelpers validationMessageTagHelper.Generator = generator.Object; // Act - await validationMessageTagHelper.ProcessAsync(context: null, output: output); + await validationMessageTagHelper.ProcessAsync(context, output: output); + + // Assert + Assert.Equal("span", output.TagName); + Assert.Equal(2, output.Attributes.Count); + var attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("data-foo")); + Assert.Equal("bar", attribute.Value); + attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("data-hello")); + Assert.Equal("world", attribute.Value); + Assert.Equal(expectedOutputContent, output.Content); + } + + [Theory] + [InlineData("Content of validation message", "Content of validation message")] + [InlineData("\r\n \r\n", "New HTML")] + public async Task ProcessAsync_MergesTagBuilderFromGenerateValidationMessage( + string childContent, string expectedOutputContent) + { + // Arrange + var validationMessageTagHelper = new ValidationMessageTagHelper + { + For = CreateModelExpression("Hello") + }; + var output = new TagHelperOutput( + "span", + attributes: new Dictionary()); + + var context = new TagHelperContext(allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult(childContent)); + var tagBuilder = new TagBuilder("span2") + { + InnerHtml = "New HTML" + }; + tagBuilder.Attributes.Add("data-foo", "bar"); + tagBuilder.Attributes.Add("data-hello", "world"); + + var generator = new Mock(MockBehavior.Strict); + var setup = generator + .Setup(mock => mock.GenerateValidationMessage( + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny(), + It.IsAny())) + .Returns(tagBuilder); + var viewContext = CreateViewContext(); + validationMessageTagHelper.ViewContext = viewContext; + validationMessageTagHelper.Generator = generator.Object; + + // Act + await validationMessageTagHelper.ProcessAsync(context, output: output); // Assert Assert.Equal("span", output.TagName); @@ -150,10 +232,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { // Arrange var validationMessageTagHelper = new ValidationMessageTagHelper(); + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var output = new TagHelperOutput( "span", - attributes: new Dictionary(), - content: "Content of validation message"); + attributes: new Dictionary()) + { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, + }; var viewContext = CreateViewContext(); var generator = new Mock(MockBehavior.Strict); validationMessageTagHelper.ViewContext = viewContext; @@ -165,7 +254,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Assert Assert.Equal("span", output.TagName); Assert.Empty(output.Attributes); - Assert.Equal("Content of validation message", output.Content); + Assert.Equal(expectedPreContent, output.PreContent); + Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); } private static ModelExpression CreateModelExpression(string name) diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs index bdad85e1a3..5cfeddef10 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs @@ -40,14 +40,23 @@ namespace Microsoft.AspNet.Mvc.TagHelpers ValidationSummary = ValidationSummary.All, }; - var tagHelperContext = new TagHelperContext(new Dictionary(), uniqueId: "test"); + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var tagHelperContext = new TagHelperContext( + allAttributes: new Dictionary(), + uniqueId: "test", + getChildContentAsync: () => Task.FromResult("Something")); var output = new TagHelperOutput( expectedTagName, attributes: new Dictionary { { "class", "form-control" } - }, - content: "Custom Content"); + }) + { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = "Custom Content", + }; var htmlGenerator = new TestableHtmlGenerator(metadataProvider); Model model = null; @@ -64,8 +73,10 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal("form-control validation-summary-valid", attribute.Value); attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("data-valmsg-summary")); Assert.Equal("true", attribute.Value); + Assert.Equal(expectedPreContent, output.PreContent); + Assert.Equal(expectedContent, output.Content); Assert.Equal("Custom Content
  • " + Environment.NewLine + "
", - output.Content); + output.PostContent); Assert.Equal(expectedTagName, output.TagName); } @@ -81,10 +92,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { ValidationSummary = validationSummary, }; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var output = new TagHelperOutput( "div", - attributes: new Dictionary(), - content: "Content of validation summary"); + attributes: new Dictionary()) + { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, + }; var expectedViewContext = CreateViewContext(); var generator = new Mock(); generator @@ -105,7 +123,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers generator.Verify(); Assert.Equal("div", output.TagName); Assert.Empty(output.Attributes); - Assert.Equal("Content of validation summary", output.Content); + Assert.Equal(expectedPreContent, output.PreContent); + Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); } [Fact] @@ -116,10 +136,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { ValidationSummary = ValidationSummary.ModelOnly, }; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; var output = new TagHelperOutput( "div", - attributes: new Dictionary(), - content: "Content of validation summary"); + attributes: new Dictionary()) + { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = "Content of validation summary" + }; var tagBuilder = new TagBuilder("span2") { InnerHtml = "New HTML" @@ -154,7 +180,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal("world", attribute.Value); attribute = Assert.Single(output.Attributes, kvp => kvp.Key.Equals("anything")); Assert.Equal("something", attribute.Value); - Assert.Equal("Content of validation summaryNew HTML", output.Content); + Assert.Equal(expectedPreContent, output.PreContent); + Assert.Equal(expectedContent, output.Content); + Assert.Equal("Content of validation summaryNew HTML", output.PostContent); } [Fact] @@ -165,10 +193,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { ValidationSummary = ValidationSummary.None, }; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; + var expectedPostContent = "original post-content"; var output = new TagHelperOutput( "div", - attributes: new Dictionary(), - content: "Content of validation message"); + attributes: new Dictionary()) + { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = expectedPostContent, + }; var generator = new Mock(MockBehavior.Strict); var viewContext = CreateViewContext(); @@ -181,7 +216,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Assert Assert.Equal("div", output.TagName); Assert.Empty(output.Attributes); - Assert.Equal("Content of validation message", output.Content); + Assert.Equal(expectedPreContent, output.PreContent); + Assert.Equal(expectedContent, output.Content); + Assert.Equal(expectedPostContent, output.PostContent); } [Theory] @@ -194,10 +231,16 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { ValidationSummary = validationSummary, }; + var expectedPreContent = "original pre-content"; + var expectedContent = "original content"; var output = new TagHelperOutput( "div", - attributes: new Dictionary(), - content: "Content of validation message"); + attributes: new Dictionary()) + { + PreContent = expectedPreContent, + Content = expectedContent, + PostContent = "Content of validation message", + }; var tagBuilder = new TagBuilder("span2") { InnerHtml = "New HTML" @@ -223,7 +266,9 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Assert Assert.Equal("div", output.TagName); Assert.Empty(output.Attributes); - Assert.Equal("Content of validation messageNew HTML", output.Content); + Assert.Equal(expectedPreContent, output.PreContent); + Assert.Equal(expectedContent, output.Content); + Assert.Equal("Content of validation messageNew HTML", output.PostContent); generator.Verify(); } diff --git a/test/WebSites/ActivatorWebSite/TagHelpers/HiddenTagHelper.cs b/test/WebSites/ActivatorWebSite/TagHelpers/HiddenTagHelper.cs index f57d7daf0f..b031b4a14f 100644 --- a/test/WebSites/ActivatorWebSite/TagHelpers/HiddenTagHelper.cs +++ b/test/WebSites/ActivatorWebSite/TagHelpers/HiddenTagHelper.cs @@ -1,10 +1,10 @@ // 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.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor.Runtime.TagHelpers; -using System.Threading.Tasks; namespace ActivatorWebSite.TagHelpers { diff --git a/test/WebSites/ActivatorWebSite/TagHelpers/RepeatContentTagHelper.cs b/test/WebSites/ActivatorWebSite/TagHelpers/RepeatContentTagHelper.cs index da9d85dce5..07ba4230d7 100644 --- a/test/WebSites/ActivatorWebSite/TagHelpers/RepeatContentTagHelper.cs +++ b/test/WebSites/ActivatorWebSite/TagHelpers/RepeatContentTagHelper.cs @@ -1,10 +1,10 @@ // 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.AspNet.Mvc.Rendering; using Microsoft.AspNet.Razor.Runtime.TagHelpers; -using System.Threading.Tasks; namespace ActivatorWebSite.TagHelpers { diff --git a/test/WebSites/RequestServicesWebSite/RequestScopedTagHelper.cs b/test/WebSites/RequestServicesWebSite/RequestScopedTagHelper.cs index 1b918cfe6e..d0743d66d2 100644 --- a/test/WebSites/RequestServicesWebSite/RequestScopedTagHelper.cs +++ b/test/WebSites/RequestServicesWebSite/RequestScopedTagHelper.cs @@ -7,7 +7,6 @@ using Microsoft.AspNet.Razor.TagHelpers; namespace RequestServicesWebSite { - [ContentBehavior(ContentBehavior.Replace)] public class RequestScopedTagHelper : TagHelper { [Activate] diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/ATagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/ATagHelper.cs index 65a0afb35a..40355cf6b8 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/ATagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/ATagHelper.cs @@ -8,7 +8,6 @@ using Microsoft.AspNet.Razor.TagHelpers; namespace TagHelpersWebSite.TagHelpers { - [ContentBehavior(ContentBehavior.Prepend)] public class ATagHelper : TagHelper { [Activate] @@ -31,7 +30,7 @@ namespace TagHelpersWebSite.TagHelpers output.Attributes["href"] = UrlHelper.Action(Action, Controller, methodParameters); - output.Content = "My "; + output.PreContent = "My "; } } } diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs index 678b6fd591..71a0111864 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs @@ -2,20 +2,22 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text.RegularExpressions; +using System.Threading.Tasks; using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Razor.TagHelpers; namespace TagHelpersWebSite.TagHelpers { [HtmlElementName("p")] - [ContentBehavior(ContentBehavior.Modify)] public class AutoLinkerTagHelper : TagHelper { - public override void Process(TagHelperContext context, TagHelperOutput output) + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) { + var childContent = await context.GetChildContentAsync(); + // Find Urls in the content and replace them with their anchor tag equivalent. output.Content = Regex.Replace( - output.Content, + childContent, @"\b(?:https?://|www\.)(\S+)\b", "$0"); } diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs index 290c30a6c8..9bf4b13116 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs @@ -7,7 +7,6 @@ using Microsoft.AspNet.Razor.TagHelpers; namespace TagHelpersWebSite.TagHelpers { [HtmlElementName("div", "style", "p")] - [ContentBehavior(ContentBehavior.Modify)] public class ConditionTagHelper : TagHelper { public bool? Condition { get; set; } @@ -17,8 +16,7 @@ namespace TagHelpersWebSite.TagHelpers // If a condition is set and evaluates to false, don't render the tag. if (Condition.HasValue && !Condition.Value) { - output.TagName = null; - output.Content = null; + output.SuppressOutput(); } } } diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewStartTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewStartTagHelper.cs index cd07dabb7e..8ef3daa358 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewStartTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewStartTagHelper.cs @@ -7,7 +7,6 @@ using Microsoft.AspNet.Razor.TagHelpers; namespace TagHelpersWebSite.TagHelpers { [HtmlElementName("nested")] - [ContentBehavior(ContentBehavior.Modify)] public class NestedViewStartTagHelper : TagHelper { public override void Process(TagHelperContext context, TagHelperOutput output) diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs index be22553fc7..c1d6f211c2 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs @@ -33,7 +33,10 @@ namespace TagHelpersWebSite.TagHelpers public override void Process(TagHelperContext context, TagHelperOutput output) { - if (MakePretty.HasValue && !MakePretty.Value) + // Need to check if output.TagName == null in-case the ConditionTagHelper calls into SuppressOutput and + // therefore sets the TagName to null. + if (MakePretty.HasValue && !MakePretty.Value || + output.TagName == null) { return; } diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs index c25be05c1a..d5d9fa15d0 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs @@ -7,7 +7,6 @@ using Microsoft.AspNet.Razor.TagHelpers; namespace TagHelpersWebSite.TagHelpers { [HtmlElementName("root")] - [ContentBehavior(ContentBehavior.Replace)] public class RootViewStartTagHelper : TagHelper { public override void Process(TagHelperContext context, TagHelperOutput output) diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs index 4bfcdea267..a356c0b98a 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs @@ -14,7 +14,6 @@ namespace MvcSample.Web.Components { [HtmlElementName("tag-cloud")] [ViewComponent(Name = "Tags")] - [ContentBehavior(ContentBehavior.Replace)] public class TagCloudViewComponentTagHelper : ITagHelper { private static readonly string[] Tags = diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/WebsiteInformationTagHelper.cs b/test/WebSites/TagHelpersWebSite/TagHelpers/WebsiteInformationTagHelper.cs index c98e1f152a..8647e6acac 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpers/WebsiteInformationTagHelper.cs +++ b/test/WebSites/TagHelpersWebSite/TagHelpers/WebsiteInformationTagHelper.cs @@ -3,12 +3,10 @@ using System; using Microsoft.AspNet.Razor.Runtime.TagHelpers; -using Microsoft.AspNet.Razor.TagHelpers; using TagHelpersWebSite.Models; namespace TagHelpersWebSite.TagHelpers { - [ContentBehavior(ContentBehavior.Append)] public class WebsiteInformationTagHelper : TagHelper { public WebsiteContext Info { get; set; } @@ -16,7 +14,7 @@ namespace TagHelpersWebSite.TagHelpers public override void Process(TagHelperContext context, TagHelperOutput output) { output.TagName = "section"; - output.Content = string.Format( + output.PostContent = string.Format( "

Version: {0}

" + Environment.NewLine + "

Copyright Year: {1}

" + Environment.NewLine + "

Approved: {2}

" + Environment.NewLine + From 80ada8d01b099b58456354d1f9b37d38732cc82d Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 7 Jan 2015 12:43:55 -0800 Subject: [PATCH 092/118] Introducing 'cache' tag helper Fixes #1552 --- .../CacheTagHelper.cs | 266 ++++++++ .../project.json | 4 +- src/Microsoft.AspNet.Mvc/MvcServices.cs | 4 + src/Microsoft.AspNet.Mvc/project.json | 3 +- ...sPartialViewsAndViewComponents.Assert1.txt | 15 + ...sPartialViewsAndViewComponents.Assert2.txt | 15 + ...sPartialViewsAndViewComponents.Assert3.txt | 15 + .../MvcTagHelpersTests.cs | 192 +++++- .../CacheTagHelperTest.cs | 634 ++++++++++++++++++ .../Components/SplashViewComponent.cs | 19 + .../Catalog_CacheTagHelperController.cs | 65 ++ .../ConfirmPayment.cshtml | 3 + .../Catalog_CacheTagHelper/Details.cshtml | 4 + .../PastPurchases.cshtml | 3 + .../ShoppingCart.cshtml | 3 + .../Catalog_CacheTagHelper/Splash.cshtml | 8 + .../_SplashPartial.cshtml | 5 + .../Catalog_CacheTagHelper/_ViewStart.cshtml | 1 + .../Shared/Components/Splash/Default.cshtml | 7 + 19 files changed, 1263 insertions(+), 3 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert1.txt create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert2.txt create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert3.txt create mode 100644 test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Components/SplashViewComponent.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ConfirmPayment.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Details.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/PastPurchases.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ShoppingCart.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Splash.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/_SplashPartial.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/_ViewStart.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Splash/Default.cshtml diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs new file mode 100644 index 0000000000..6d8e8d288c --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs @@ -0,0 +1,266 @@ +// 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.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.Http; +using Microsoft.AspNet.Razor.Runtime.TagHelpers; +using Microsoft.Framework.Cache.Memory; + +namespace Microsoft.AspNet.Mvc.TagHelpers +{ + /// + /// implementation targeting <cache> elements. + /// + public class CacheTagHelper : TagHelper + { + /// + /// Prefix used by instances when creating entries in . + /// + public static readonly string CacheKeyPrefix = nameof(CacheTagHelper); + private const string VaryByAttributeName = "vary-by"; + private const string VaryByHeaderAttributeName = "vary-by-header"; + private const string VaryByQueryAttributeName = "vary-by-query"; + private const string VaryByRouteAttributeName = "vary-by-route"; + private const string VaryByCookieAttributeName = "vary-by-cookie"; + private const string VaryByUserAttributeName = "vary-by-user"; + private const string ExpiresOnAttributeName = "expires-on"; + private const string ExpiresAfterAttributeName = "expires-after"; + private const string ExpiresSlidingAttributeName = "expires-sliding"; + private const string CachePriorityAttributeName = "priority"; + private const string CacheKeyTokenSeparator = "||"; + private static readonly char[] AttributeSeparator = new[] { ',' }; + + /// + /// Gets or sets the instance used to cache entries. + /// + [Activate] + protected internal IMemoryCache MemoryCache { get; set; } + + /// + /// Gets or sets the for the current executing View. + /// + [Activate] + protected internal ViewContext ViewContext { get; set; } + + /// + /// Gets or sets a to vary the cached result by. + /// + [HtmlAttributeName(VaryByAttributeName)] + public string VaryBy { get; set; } + + /// + /// Gets or sets the name of a HTTP request header to vary the cached result by. + /// + [HtmlAttributeName(VaryByHeaderAttributeName)] + public string VaryByHeader { get; set; } + + /// + /// Gets or sets a comma-delimited set of query parameters to vary the cached result by. + /// + [HtmlAttributeName(VaryByQueryAttributeName)] + public string VaryByQuery { get; set; } + + /// + /// Gets or sets a comma-delimited set of route data parameters to vary the cached result by. + /// + [HtmlAttributeName(VaryByRouteAttributeName)] + public string VaryByRoute { get; set; } + + /// + /// Gets or sets a comma-delimited set of cookie names to vary the cached result by. + /// + [HtmlAttributeName(VaryByCookieAttributeName)] + public string VaryByCookie { get; set; } + + /// + /// Gets or sets a value that determines if the cached result is to be varied by the Identity for the logged in + /// . + /// + [HtmlAttributeName(VaryByUserAttributeName)] + public bool VaryByUser { get; set; } + + /// + /// Gets or sets the exact the cache entry should be evicted. + /// + [HtmlAttributeName(ExpiresOnAttributeName)] + public DateTimeOffset? ExpiresOn { get; set; } + + /// + /// Gets or sets the duration, from the time the cache entry was added, when it should be evicted. + /// + [HtmlAttributeName(ExpiresAfterAttributeName)] + public TimeSpan? ExpiresAfter { get; set; } + + /// + /// Gets or sets the duration from last access that the cache entry should be evicted. + /// + [HtmlAttributeName(ExpiresSlidingAttributeName)] + public TimeSpan? ExpiresSliding { get; set; } + + /// + /// Gets or sets the policy for the cache entry. + /// + [HtmlAttributeName(CachePriorityAttributeName)] + public CachePreservationPriority? Priority { get; set; } + + /// + public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var key = GenerateKey(context); + string result; + if (!MemoryCache.TryGetValue(key, out result)) + { + result = await context.GetChildContentAsync(); + MemoryCache.Set(key, cacheSetContext => + { + UpdateCacheContext(cacheSetContext); + return result; + }); + } + + // Clear the contents of the "cache" element since we don't want to render it. + output.SuppressOutput(); + + output.Content = result; + } + + // Internal for unit testing + internal string GenerateKey(TagHelperContext context) + { + var builder = new StringBuilder(CacheKeyPrefix); + builder.Append(CacheKeyTokenSeparator) + .Append(context.UniqueId); + + var request = ViewContext.HttpContext.Request; + + if (!string.IsNullOrEmpty(VaryBy)) + { + builder.Append(CacheKeyTokenSeparator) + .Append(nameof(VaryBy)) + .Append(CacheKeyTokenSeparator) + .Append(VaryBy); + } + + AddStringCollectionKey(builder, nameof(VaryByCookie), VaryByCookie, request.Cookies); + AddStringCollectionKey(builder, nameof(VaryByHeader), VaryByHeader, request.Headers); + AddStringCollectionKey(builder, nameof(VaryByQuery), VaryByQuery, request.Query); + AddVaryByRouteKey(builder); + + if (VaryByUser) + { + builder.Append(CacheKeyTokenSeparator) + .Append(nameof(VaryByUser)) + .Append(CacheKeyTokenSeparator) + .Append(ViewContext.HttpContext.User?.Identity?.Name); + } + + // The key is typically too long to be useful, so we use a cryptographic hash + // as the actual key (better randomization and key distribution, so small vary + // values will generate dramatically different keys). + using (var sha = SHA256.Create()) + { + var contentBytes = Encoding.UTF8.GetBytes(builder.ToString()); + var hashedBytes = sha.ComputeHash(contentBytes); + return Convert.ToBase64String(hashedBytes); + } + } + + // Internal for unit testing + internal void UpdateCacheContext(ICacheSetContext cacheSetContext) + { + if (ExpiresOn != null) + { + cacheSetContext.SetAbsoluteExpiration(ExpiresOn.Value); + } + + if (ExpiresAfter != null) + { + cacheSetContext.SetAbsoluteExpiration(ExpiresAfter.Value); + } + + if (ExpiresSliding != null) + { + cacheSetContext.SetSlidingExpiration(ExpiresSliding.Value); + } + + if (Priority != null) + { + cacheSetContext.SetPriority(Priority.Value); + } + } + + private static void AddStringCollectionKey(StringBuilder builder, + string keyName, + string value, + IReadableStringCollection sourceCollection) + { + if (!string.IsNullOrEmpty(value)) + { + // keyName(param1=value1|param2=value2) + builder.Append(CacheKeyTokenSeparator) + .Append(keyName) + .Append("("); + + var tokenFound = false; + foreach (var item in Tokenize(value)) + { + tokenFound = true; + + builder.Append(item) + .Append(CacheKeyTokenSeparator) + .Append(sourceCollection[item]) + .Append(CacheKeyTokenSeparator); + } + + if (tokenFound) + { + // Remove the trailing separator + builder.Length -= CacheKeyTokenSeparator.Length; + } + + builder.Append(")"); + } + } + + private void AddVaryByRouteKey(StringBuilder builder) + { + var tokenFound = false; + + if (!string.IsNullOrEmpty(VaryByRoute)) + { + builder.Append(CacheKeyTokenSeparator) + .Append(nameof(VaryByRoute)) + .Append("("); + + foreach (var route in Tokenize(VaryByRoute)) + { + tokenFound = true; + + builder.Append(route) + .Append(CacheKeyTokenSeparator) + .Append(ViewContext.RouteData.Values[route]) + .Append(CacheKeyTokenSeparator); + } + + if (tokenFound) + { + builder.Length -= CacheKeyTokenSeparator.Length; + } + + builder.Append(")"); + } + } + + private static IEnumerable Tokenize(string value) + { + return value.Split(AttributeSeparator, StringSplitOptions.RemoveEmptyEntries) + .Select(token => token.Trim()) + .Where(token => token.Length > 0); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/project.json b/src/Microsoft.AspNet.Mvc.TagHelpers/project.json index 8ca5e62a7a..9a0034ef94 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/project.json +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/project.json @@ -6,7 +6,9 @@ }, "dependencies": { "Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" }, - "Microsoft.AspNet.Mvc.Razor": "" + "Microsoft.AspNet.Mvc.Razor": "6.0.0-*", + "Microsoft.Framework.Cache.Memory": "1.0.0-*", + "System.Security.Cryptography.Hashing.Algorithms": "4.0.0-beta-*" }, "frameworks": { "aspnet50": {}, diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index 188b5a8ba8..1e2b5aa84f 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -13,6 +13,7 @@ using Microsoft.AspNet.Mvc.Razor.OptionDescriptors; using Microsoft.AspNet.Mvc.Rendering; using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Security; +using Microsoft.Framework.Cache.Memory; using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.NestedProviders; @@ -138,6 +139,9 @@ namespace Microsoft.AspNet.Mvc // Only want one ITagHelperActivator so it can cache Type activation information. Types won't conflict. yield return describe.Singleton(); + // Consumed by the Cache tag helper to cache results across the lifetime of the application. + yield return describe.Singleton(); + // DefaultHtmlGenerator is pretty much stateless but depends on Scoped services such as IUrlHelper and // IActionBindingContextProvider. Therefore it too is scoped. yield return describe.Transient(); diff --git a/src/Microsoft.AspNet.Mvc/project.json b/src/Microsoft.AspNet.Mvc/project.json index 3d1b2ac692..d4d04fb2d4 100644 --- a/src/Microsoft.AspNet.Mvc/project.json +++ b/src/Microsoft.AspNet.Mvc/project.json @@ -6,7 +6,8 @@ }, "dependencies": { "Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" }, - "Microsoft.AspNet.Mvc.Razor": "6.0.0-*" + "Microsoft.AspNet.Mvc.Razor": "6.0.0-*", + "Microsoft.Framework.Cache.Memory": "1.0.0-*" }, "frameworks": { "aspnet50": {}, diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert1.txt b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert1.txt new file mode 100644 index 0000000000..a0a21b6c33 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert1.txt @@ -0,0 +1,15 @@ +

Category: Laptops

+

Region: North

+ +

Cached content

+ Locations closest to your locale: + +NorthWest Store +
CorrelationId in View Component: 1
+ + Listing items + +Cached Content for Laptops +
CorrelationId in Partial: 1
+ +
CorrelationId in Splash: 1
\ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert2.txt b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert2.txt new file mode 100644 index 0000000000..afd10d8186 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert2.txt @@ -0,0 +1,15 @@ +

Category: Phones

+

Region: North

+ +

Cached content

+ Locations closest to your locale: + +NorthWest Store +
CorrelationId in View Component: 1
+ + Listing items + +Cached Content for Phones +
CorrelationId in Partial: 2
+ +
CorrelationId in Splash: 2
\ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert3.txt b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert3.txt new file mode 100644 index 0000000000..2b07849a8b --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert3.txt @@ -0,0 +1,15 @@ +

Category: Phones

+

Region: East

+ +

Cached content

+ Locations closest to your locale: + +Nationwide Store +
CorrelationId in View Component: 3
+ + Listing items + +Cached Content for Phones +
CorrelationId in Partial: 2
+ +
CorrelationId in Splash: 3
\ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs index 7f39fe9c82..fc975bae58 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests { private readonly IServiceProvider _provider = TestHelper.CreateServices("MvcTagHelpersWebSite"); private readonly Action _app = new Startup().Configure; - private static readonly Assembly _resourcesAssembly = typeof(TagHelpersTests).GetTypeInfo().Assembly; + private static readonly Assembly _resourcesAssembly = typeof(MvcTagHelpersTests).GetTypeInfo().Assembly; [Theory] [InlineData("Index", null)] @@ -96,5 +96,195 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests expectedContent = string.Format(expectedContent, forgeryToken); Assert.Equal(expectedContent.Trim(), responseContent.Trim()); } + + [Fact] + public async Task CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents() + { + // Arrange + var assertFile = + "compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert"; + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + client.BaseAddress = new Uri("http://localhost"); + client.DefaultRequestHeaders.Add("Locale", "North"); + + // Act - 1 + // Verify that content gets cached based on vary-by-params + var targetUrl = "/catalog?categoryId=1&correlationid=1"; + var response1 = await client.GetStringAsync(targetUrl); + var response2 = await client.GetStringAsync(targetUrl); + + // Assert - 1 + var expected1 = await _resourcesAssembly.ReadResourceAsStringAsync(assertFile + "1.txt"); + + Assert.Equal(expected1, response1.Trim()); + Assert.Equal(expected1, response2.Trim()); + + // Act - 2 + // Verify content gets changed in partials when one of the vary by parameters is changed + targetUrl = "/catalog?categoryId=3&correlationid=2"; + var response3 = await client.GetStringAsync(targetUrl); + var response4 = await client.GetStringAsync(targetUrl); + + // Assert - 2 + var expected2 = await _resourcesAssembly.ReadResourceAsStringAsync(assertFile + "2.txt"); + + Assert.Equal(expected2, response3.Trim()); + Assert.Equal(expected2, response4.Trim()); + + // Act - 3 + // Verify content gets changed in a View Component when the Vary-by-header parameters is changed + client.DefaultRequestHeaders.Remove("Locale"); + client.DefaultRequestHeaders.Add("Locale", "East"); + + targetUrl = "/catalog?categoryId=3&correlationid=3"; + var response5 = await client.GetStringAsync(targetUrl); + var response6 = await client.GetStringAsync(targetUrl); + + // Assert - 3 + var expected3 = await _resourcesAssembly.ReadResourceAsStringAsync(assertFile + "3.txt"); + + Assert.Equal(expected3, response5.Trim()); + Assert.Equal(expected3, response6.Trim()); + } + + [Fact] + public async Task CacheTagHelper_ExpiresContent_BasedOnExpiresParameter() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + client.BaseAddress = new Uri("http://localhost"); + + // Act - 1 + var response1 = await client.GetStringAsync("/catalog/2"); + + // Assert - 1 + var expected1 = "Cached content for 2"; + Assert.Equal(expected1, response1.Trim()); + + // Act - 2 + await Task.Delay(TimeSpan.FromSeconds(1)); + var response2 = await client.GetStringAsync("/catalog/3"); + + // Assert - 2 + var expected2 = "Cached content for 3"; + Assert.Equal(expected2, response2.Trim()); + } + + [Fact] + public async Task CacheTagHelper_UsesVaryByCookie_ToVaryContent() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + client.BaseAddress = new Uri("http://localhost"); + + // Act - 1 + var response1 = await client.GetStringAsync("/catalog/cart?correlationid=1"); + + // Assert - 1 + var expected1 = "Cart content for 1"; + Assert.Equal(expected1, response1.Trim()); + + // Act - 2 + client.DefaultRequestHeaders.Add("Cookie", "CartId=10"); + var response2 = await client.GetStringAsync("/catalog/cart?correlationid=2"); + + // Assert - 2 + var expected2 = "Cart content for 2"; + Assert.Equal(expected2, response2.Trim()); + + // Act - 3 + // Resend the cookiesless request and cached result from the first response. + client.DefaultRequestHeaders.Remove("Cookie"); + var response3 = await client.GetStringAsync("/catalog/cart?correlationid=3"); + + // Assert - 3 + Assert.Equal(expected1, response3.Trim()); + } + + [Fact] + public async Task CacheTagHelper_VariesByRoute() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + client.BaseAddress = new Uri("http://localhost"); + + // Act - 1 + var response1 = await client.GetStringAsync( + "/catalog/north-west/confirm-payment?confirmationId=1"); + + // Assert - 1 + var expected1 = "Welcome Guest. Your confirmation id is 1. (Region north-west)"; + Assert.Equal(expected1, response1.Trim()); + + // Act - 2 + var response2 = await client.GetStringAsync( + "/catalog/south-central/confirm-payment?confirmationId=2"); + + // Assert - 2 + var expected2 = "Welcome Guest. Your confirmation id is 2. (Region south-central)"; + Assert.Equal(expected2, response2.Trim()); + + // Act 3 + var response3 = await client.GetStringAsync( + "/catalog/north-west/Silver/confirm-payment?confirmationId=4"); + + var expected3 = "Welcome Silver member. Your confirmation id is 4. (Region north-west)"; + Assert.Equal(expected3, response3.Trim()); + + // Act 4 + var response4 = await client.GetStringAsync( + "/catalog/north-west/Gold/confirm-payment?confirmationId=5"); + + var expected4 = "Welcome Gold member. Your confirmation id is 5. (Region north-west)"; + Assert.Equal(expected4, response4.Trim()); + + // Act - 4 + // Resend the responses and expect cached results. + response1 = await client.GetStringAsync( + "/catalog/north-west/confirm-payment?confirmationId=301"); + response2 = await client.GetStringAsync( + "/catalog/south-central/confirm-payment?confirmationId=402"); + response3 = await client.GetStringAsync( + "/catalog/north-west/Silver/confirm-payment?confirmationId=503"); + response4 = await client.GetStringAsync( + "/catalog/north-west/Gold/confirm-payment?confirmationId=608"); + + // Assert - 4 + Assert.Equal(expected1, response1.Trim()); + Assert.Equal(expected2, response2.Trim()); + Assert.Equal(expected3, response3.Trim()); + Assert.Equal(expected4, response4.Trim()); + } + + [Fact] + public async Task CacheTagHelper_VariesByUserId() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + client.BaseAddress = new Uri("http://localhost"); + + // Act - 1 + var response1 = await client.GetStringAsync("/catalog/past-purchases/test1?correlationid=1"); + var response2 = await client.GetStringAsync("/catalog/past-purchases/test1?correlationid=2"); + + // Assert - 1 + var expected1 = "Past purchases for user test1 (1)"; + Assert.Equal(expected1, response1.Trim()); + Assert.Equal(expected1, response2.Trim()); + + // Act - 2 + var response3 = await client.GetStringAsync("/catalog/past-purchases/test2?correlationid=3"); + var response4 = await client.GetStringAsync("/catalog/past-purchases/test2?correlationid=4"); + + // Assert - 2 + var expected2 = "Past purchases for user test2 (3)"; + Assert.Equal(expected2, response3.Trim()); + Assert.Equal(expected2, response4.Trim()); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs new file mode 100644 index 0000000000..923eaf9446 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs @@ -0,0 +1,634 @@ +// 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.IO; +using System.Security.Claims; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNet.Mvc.ModelBinding; +using Microsoft.AspNet.Mvc.Rendering; +using Microsoft.AspNet.Http.Core; +using Microsoft.AspNet.Razor.Runtime.TagHelpers; +using Microsoft.AspNet.Routing; +using Microsoft.Framework.Cache.Memory; +using Microsoft.Framework.Cache.Memory.Infrastructure; +using Moq; +using Xunit; + +namespace Microsoft.AspNet.Mvc.TagHelpers +{ + public class CacheTagHelperTest + { + [Fact] + public void GenerateKey_ReturnsKeyBasedOnTagHelperUniqueId() + { + // Arrange + var id = Guid.NewGuid().ToString(); + var tagHelperContext = GetTagHelperContext(id); + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext() + }; + var expected = GetHashedBytes("CacheTagHelper||" + id); + + // Act + var key = cacheTagHelper.GenerateKey(tagHelperContext); + + // Assert + Assert.Equal(expected, key); + } + + [Theory] + [InlineData("Vary-By-Value")] + [InlineData("Vary with spaces")] + [InlineData(" Vary with more spaces ")] + public void GenerateKey_UsesVaryByPropertyToGenerateKey(string varyBy) + { + // Arrange + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + VaryBy = varyBy + }; + var expected = GetHashedBytes("CacheTagHelper||testid||VaryBy||" + varyBy); + + // Act + var key = cacheTagHelper.GenerateKey(tagHelperContext); + + // Assert + Assert.Equal(expected, key); + } + + [Theory] + [InlineData("Cookie0", "CacheTagHelper||testid||VaryByCookie(Cookie0||Cookie0Value)")] + [InlineData("Cookie0,Cookie1", + "CacheTagHelper||testid||VaryByCookie(Cookie0||Cookie0Value||Cookie1||Cookie1Value)")] + [InlineData("Cookie0, Cookie1", + "CacheTagHelper||testid||VaryByCookie(Cookie0||Cookie0Value||Cookie1||Cookie1Value)")] + [InlineData(" Cookie0, , Cookie1 ", + "CacheTagHelper||testid||VaryByCookie(Cookie0||Cookie0Value||Cookie1||Cookie1Value)")] + [InlineData(",Cookie0,,Cookie1,", + "CacheTagHelper||testid||VaryByCookie(Cookie0||Cookie0Value||Cookie1||Cookie1Value)")] + public void GenerateKey_UsesVaryByCookieName(string varyByCookie, string expected) + { + // Arrange + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + VaryByCookie = varyByCookie + }; + cacheTagHelper.ViewContext.HttpContext.Request.Headers["Cookie"] = + "Cookie0=Cookie0Value;Cookie1=Cookie1Value"; + + // Act + var key = cacheTagHelper.GenerateKey(tagHelperContext); + + // Assert + Assert.Equal(GetHashedBytes(expected), key); + } + + [Theory] + [InlineData("Accept-Language", "CacheTagHelper||testid||VaryByHeader(Accept-Language||en-us;charset=utf8)")] + [InlineData("X-CustomHeader,Accept-Encoding, NotAvailable", + "CacheTagHelper||testid||VaryByHeader(X-CustomHeader||Header-Value||Accept-Encoding||utf8||NotAvailable||)")] + [InlineData("X-CustomHeader, , Accept-Encoding, NotAvailable", + "CacheTagHelper||testid||VaryByHeader(X-CustomHeader||Header-Value||Accept-Encoding||utf8||NotAvailable||)")] + public void GenerateKey_UsesVaryByHeader(string varyByHeader, string expected) + { + // Arrange + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + VaryByHeader = varyByHeader + }; + var headers = cacheTagHelper.ViewContext.HttpContext.Request.Headers; + headers["Accept-Language"] = "en-us;charset=utf8"; + headers["Accept-Encoding"] = "utf8"; + headers["X-CustomHeader"] = "Header-Value"; + + // Act + var key = cacheTagHelper.GenerateKey(tagHelperContext); + + // Assert + Assert.Equal(GetHashedBytes(expected), key); + } + + [Theory] + [InlineData("category", "CacheTagHelper||testid||VaryByQuery(category||cats)")] + [InlineData("Category,SortOrder,SortOption", + "CacheTagHelper||testid||VaryByQuery(Category||cats||SortOrder||||SortOption||Adorability)")] + [InlineData("Category, SortOrder, SortOption, ", + "CacheTagHelper||testid||VaryByQuery(Category||cats||SortOrder||||SortOption||Adorability)")] + public void GenerateKey_UsesVaryByQuery(string varyByQuery, string expected) + { + // Arrange + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + VaryByQuery = varyByQuery + }; + cacheTagHelper.ViewContext.HttpContext.Request.QueryString = + new Http.QueryString("?sortoption=Adorability&Category=cats&sortOrder="); + + // Act + var key = cacheTagHelper.GenerateKey(tagHelperContext); + + // Assert + Assert.Equal(GetHashedBytes(expected), key); + } + + [Theory] + [InlineData("id", "CacheTagHelper||testid||VaryByRoute(id||4)")] + [InlineData("Category,,Id,OptionRouteValue", + "CacheTagHelper||testid||VaryByRoute(Category||MyCategory||Id||4||OptionRouteValue||)")] + [InlineData(" Category, , Id, OptionRouteValue, ", + "CacheTagHelper||testid||VaryByRoute(Category||MyCategory||Id||4||OptionRouteValue||)")] + public void GenerateKey_UsesVaryByRoute(string varyByRoute, string expected) + { + // Arrange + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + VaryByRoute = varyByRoute + }; + cacheTagHelper.ViewContext.RouteData.Values["id"] = 4; + cacheTagHelper.ViewContext.RouteData.Values["category"] = "MyCategory"; + + // Act + var key = cacheTagHelper.GenerateKey(tagHelperContext); + + // Assert + Assert.Equal(GetHashedBytes(expected), key); + } + + [Fact] + public void GenerateKey_UsesVaryByUser_WhenUserIsNotAuthenticated() + { + // Arrange + var expected = "CacheTagHelper||testid||VaryByUser||"; + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + VaryByUser = true + }; + + // Act + var key = cacheTagHelper.GenerateKey(tagHelperContext); + + // Assert + Assert.Equal(GetHashedBytes(expected), key); + } + + [Fact] + public void GenerateKey_UsesVaryByUserAndAuthenticatedUserName() + { + // Arrange + var expected = "CacheTagHelper||testid||VaryByUser||test_name"; + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + VaryByUser = true + }; + var identity = new ClaimsIdentity(new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, "test_name") }); + cacheTagHelper.ViewContext.HttpContext.User = new ClaimsPrincipal(identity); + + // Act + var key = cacheTagHelper.GenerateKey(tagHelperContext); + + // Assert + Assert.Equal(GetHashedBytes(expected), key); + } + + [Fact] + public void GenerateKey_WithMultipleVaryByOptions_CreatesCombinedKey() + { + // Arrange + var expected = GetHashedBytes("CacheTagHelper||testid||VaryBy||custom-value||" + + "VaryByHeader(content-type||text/html)||VaryByUser||someuser"); + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + VaryByUser = true, + VaryByHeader = "content-type", + VaryBy = "custom-value" + }; + cacheTagHelper.ViewContext.HttpContext.Request.Headers["Content-Type"] = "text/html"; + var identity = new ClaimsIdentity(new[] { new Claim(ClaimsIdentity.DefaultNameClaimType, "someuser") }); + cacheTagHelper.ViewContext.HttpContext.User = new ClaimsPrincipal(identity); + + // Act + var key = cacheTagHelper.GenerateKey(tagHelperContext); + + // Assert + Assert.Equal(expected, key); + } + + [Fact] + public async Task ProcessAsync_ReturnsCachedValue_IfVaryByParamIsUnchanged() + { + // Arrange - 1 + var id = "unique-id"; + var childContent = "original-child-content"; + var cache = new MemoryCache(new MemoryCacheOptions()); + var tagHelperContext1 = GetTagHelperContext(id, childContent); + var tagHelperOutput1 = new TagHelperOutput("cache", new Dictionary()); + var cacheTagHelper1 = new CacheTagHelper + { + VaryByQuery = "key1,key2", + ViewContext = GetViewContext(), + MemoryCache = cache + }; + cacheTagHelper1.ViewContext.HttpContext.Request.QueryString = new Http.QueryString( + "?key1=value1&key2=value2"); + + // Act - 1 + await cacheTagHelper1.ProcessAsync(tagHelperContext1, tagHelperOutput1); + + // Assert - 1 + Assert.Null(tagHelperOutput1.PreContent); + Assert.Null(tagHelperOutput1.PostContent); + Assert.True(tagHelperOutput1.ContentSet); + Assert.Equal(childContent, tagHelperOutput1.Content); + + // Arrange - 2 + var tagHelperContext2 = GetTagHelperContext(id, "different-content"); + var tagHelperOutput2 = new TagHelperOutput("cache", new Dictionary()); + var cacheTagHelper2 = new CacheTagHelper + { + VaryByQuery = "key1,key2", + ViewContext = GetViewContext(), + MemoryCache = cache + }; + cacheTagHelper2.ViewContext.HttpContext.Request.QueryString = new Http.QueryString( + "?key1=value1&key2=value2"); + + // Act - 2 + await cacheTagHelper2.ProcessAsync(tagHelperContext2, tagHelperOutput2); + + // Assert - 2 + Assert.Null(tagHelperOutput2.PreContent); + Assert.Null(tagHelperOutput2.PostContent); + Assert.True(tagHelperOutput2.ContentSet); + Assert.Equal(childContent, tagHelperOutput2.Content); + } + + [Fact] + public async Task ProcessAsync_RecalculatesValueIfCacheKeyChanges() + { + // Arrange - 1 + var id = "unique-id"; + var childContent1 = "original-child-content"; + var cache = new MemoryCache(new MemoryCacheOptions()); + var tagHelperContext1 = GetTagHelperContext(id, childContent1); + var tagHelperOutput1 = new TagHelperOutput("cache", new Dictionary { { "attr", "value" } }) + { + PreContent = "", + PostContent = "" + }; + var cacheTagHelper1 = new CacheTagHelper + { + VaryByCookie = "cookie1,cookie2", + ViewContext = GetViewContext(), + MemoryCache = cache + }; + cacheTagHelper1.ViewContext.HttpContext.Request.Headers["Cookie"] = "cookie1=value1;cookie2=value2"; + + // Act - 1 + await cacheTagHelper1.ProcessAsync(tagHelperContext1, tagHelperOutput1); + + // Assert - 1 + Assert.Null(tagHelperOutput1.PreContent); + Assert.Null(tagHelperOutput1.PostContent); + Assert.True(tagHelperOutput1.ContentSet); + Assert.Equal(childContent1, tagHelperOutput1.Content); + + // Arrange - 2 + var childContent2 = "different-content"; + var tagHelperContext2 = GetTagHelperContext(id, childContent2); + var tagHelperOutput2 = new TagHelperOutput("cache", new Dictionary { { "attr", "value" } }) + { + PreContent = "", + PostContent = "" + }; + var cacheTagHelper2 = new CacheTagHelper + { + VaryByCookie = "cookie1,cookie2", + ViewContext = GetViewContext(), + MemoryCache = cache + }; + cacheTagHelper2.ViewContext.HttpContext.Request.Headers["Cookie"] = "cookie1=value1;cookie2=not-value2"; + + // Act - 2 + await cacheTagHelper2.ProcessAsync(tagHelperContext2, tagHelperOutput2); + + // Assert - 2 + Assert.Null(tagHelperOutput2.PreContent); + Assert.Null(tagHelperOutput2.PostContent); + Assert.True(tagHelperOutput2.ContentSet); + Assert.Equal(childContent2, tagHelperOutput2.Content); + } + + [Fact] + public void UpdateCacheContext_SetsAbsoluteExpiration_IfExpiresOnIsSet() + { + // Arrange + var expiresOn = DateTimeOffset.UtcNow.AddMinutes(4); + var cache = new MemoryCache(new MemoryCacheOptions()); + var cacheContext = new Mock(); + cacheContext.Setup(c => c.SetAbsoluteExpiration(expiresOn)) + .Verifiable(); + var cacheTagHelper = new CacheTagHelper + { + MemoryCache = cache, + ExpiresOn = expiresOn + }; + + // Act + cacheTagHelper.UpdateCacheContext(cacheContext.Object); + + // Assert + cacheContext.Verify(); + } + + [Fact] + public void UpdateCacheContext_SetsAbsoluteExpiration_IfExpiresAfterIsSet() + { + // Arrange + var expiresAfter = TimeSpan.FromSeconds(42); + var cache = new MemoryCache(new MemoryCacheOptions()); + var cacheContext = new Mock(); + cacheContext.Setup(c => c.SetAbsoluteExpiration(expiresAfter)) + .Verifiable(); + var cacheTagHelper = new CacheTagHelper + { + MemoryCache = cache, + ExpiresAfter = expiresAfter + }; + + // Act + cacheTagHelper.UpdateCacheContext(cacheContext.Object); + + // Assert + cacheContext.Verify(); + } + + [Fact] + public void UpdateCacheContext_SetsSlidingExpiration_IfExpiresSlidingIsSet() + { + // Arrange + var expiresSliding = TimeSpan.FromSeconds(37); + var cache = new MemoryCache(new MemoryCacheOptions()); + var cacheContext = new Mock(); + cacheContext.Setup(c => c.SetSlidingExpiration(expiresSliding)) + .Verifiable(); + var cacheTagHelper = new CacheTagHelper + { + MemoryCache = cache, + ExpiresSliding = expiresSliding + }; + + // Act + cacheTagHelper.UpdateCacheContext(cacheContext.Object); + + // Assert + cacheContext.Verify(); + } + + [Fact] + public void UpdateCacheContext_SetsCachePreservationPriority() + { + // Arrange + var priority = CachePreservationPriority.High; + var cache = new MemoryCache(new MemoryCacheOptions()); + var cacheContext = new Mock(); + cacheContext.Setup(c => c.SetPriority(priority)) + .Verifiable(); + var cacheTagHelper = new CacheTagHelper + { + MemoryCache = cache, + Priority = priority + }; + + // Act + cacheTagHelper.UpdateCacheContext(cacheContext.Object); + + // Assert + cacheContext.Verify(); + } + + [Fact] + public async Task ProcessAsync_UsesExpiresAfter_ToExpireCacheEntry() + { + // Arrange - 1 + var currentTime = new DateTimeOffset(2010, 1, 1, 0, 0, 0, TimeSpan.Zero); + var id = "unique-id"; + var childContent1 = "original-child-content"; + var clock = new Mock(); + clock.SetupGet(p => p.UtcNow) + .Returns(() => currentTime); + var cache = new MemoryCache(new MemoryCacheOptions { Clock = clock.Object }); + var tagHelperContext1 = GetTagHelperContext(id, childContent1); + var tagHelperOutput1 = new TagHelperOutput("cache", new Dictionary { { "attr", "value" } }) + { + PreContent = "", + PostContent = "" + }; + var cacheTagHelper1 = new CacheTagHelper + { + ViewContext = GetViewContext(), + MemoryCache = cache, + ExpiresAfter = TimeSpan.FromMinutes(10) + }; + + // Act - 1 + await cacheTagHelper1.ProcessAsync(tagHelperContext1, tagHelperOutput1); + + // Assert - 1 + Assert.Null(tagHelperOutput1.PreContent); + Assert.Null(tagHelperOutput1.PostContent); + Assert.True(tagHelperOutput1.ContentSet); + Assert.Equal(childContent1, tagHelperOutput1.Content); + + // Arrange - 2 + var childContent2 = "different-content"; + var tagHelperContext2 = GetTagHelperContext(id, childContent2); + var tagHelperOutput2 = new TagHelperOutput("cache", new Dictionary { { "attr", "value" } }) + { + PreContent = "", + PostContent = "" + }; + var cacheTagHelper2 = new CacheTagHelper + { + ViewContext = GetViewContext(), + MemoryCache = cache, + ExpiresAfter = TimeSpan.FromMinutes(10) + }; + currentTime = currentTime.AddMinutes(11); + + // Act - 2 + await cacheTagHelper2.ProcessAsync(tagHelperContext2, tagHelperOutput2); + + // Assert - 2 + Assert.Null(tagHelperOutput2.PreContent); + Assert.Null(tagHelperOutput2.PostContent); + Assert.True(tagHelperOutput2.ContentSet); + Assert.Equal(childContent2, tagHelperOutput2.Content); + } + + [Fact] + public async Task ProcessAsync_UsesExpiresOn_ToExpireCacheEntry() + { + // Arrange - 1 + var currentTime = new DateTimeOffset(2010, 1, 1, 0, 0, 0, TimeSpan.Zero); + var id = "unique-id"; + var childContent1 = "original-child-content"; + var clock = new Mock(); + clock.SetupGet(p => p.UtcNow) + .Returns(() => currentTime); + var cache = new MemoryCache(new MemoryCacheOptions { Clock = clock.Object }); + var tagHelperContext1 = GetTagHelperContext(id, childContent1); + var tagHelperOutput1 = new TagHelperOutput("cache", new Dictionary { { "attr", "value" } }) + { + PreContent = "", + PostContent = "" + }; + var cacheTagHelper1 = new CacheTagHelper + { + ViewContext = GetViewContext(), + MemoryCache = cache, + ExpiresOn = currentTime.AddMinutes(5) + }; + + // Act - 1 + await cacheTagHelper1.ProcessAsync(tagHelperContext1, tagHelperOutput1); + + // Assert - 1 + Assert.Null(tagHelperOutput1.PreContent); + Assert.Null(tagHelperOutput1.PostContent); + Assert.True(tagHelperOutput1.ContentSet); + Assert.Equal(childContent1, tagHelperOutput1.Content); + + // Arrange - 2 + currentTime = currentTime.AddMinutes(5).AddSeconds(2); + var childContent2 = "different-content"; + var tagHelperContext2 = GetTagHelperContext(id, childContent2); + var tagHelperOutput2 = new TagHelperOutput("cache", new Dictionary { { "attr", "value" } }) + { + PreContent = "", + PostContent = "" + }; + var cacheTagHelper2 = new CacheTagHelper + { + ViewContext = GetViewContext(), + MemoryCache = cache, + ExpiresOn = currentTime.AddMinutes(5) + }; + + // Act - 2 + await cacheTagHelper2.ProcessAsync(tagHelperContext2, tagHelperOutput2); + + // Assert - 2 + Assert.Null(tagHelperOutput2.PreContent); + Assert.Null(tagHelperOutput2.PostContent); + Assert.True(tagHelperOutput2.ContentSet); + Assert.Equal(childContent2, tagHelperOutput2.Content); + } + + [Fact] + public async Task ProcessAsync_UsesExpiresSliding_ToExpireCacheEntryWithSlidingExpiration() + { + // Arrange - 1 + var currentTime = new DateTimeOffset(2010, 1, 1, 0, 0, 0, TimeSpan.Zero); + var id = "unique-id"; + var childContent1 = "original-child-content"; + var clock = new Mock(); + clock.SetupGet(p => p.UtcNow) + .Returns(() => currentTime); + var cache = new MemoryCache(new MemoryCacheOptions { Clock = clock.Object }); + var tagHelperContext1 = GetTagHelperContext(id, childContent1); + var tagHelperOutput1 = new TagHelperOutput("cache", new Dictionary { { "attr", "value" } }) + { + PreContent = "", + PostContent = "" + }; + var cacheTagHelper1 = new CacheTagHelper + { + ViewContext = GetViewContext(), + MemoryCache = cache, + ExpiresSliding = TimeSpan.FromSeconds(30) + }; + + // Act - 1 + await cacheTagHelper1.ProcessAsync(tagHelperContext1, tagHelperOutput1); + + // Assert - 1 + Assert.Null(tagHelperOutput1.PreContent); + Assert.Null(tagHelperOutput1.PostContent); + Assert.True(tagHelperOutput1.ContentSet); + Assert.Equal(childContent1, tagHelperOutput1.Content); + + // Arrange - 2 + currentTime = currentTime.AddSeconds(35); + var childContent2 = "different-content"; + var tagHelperContext2 = GetTagHelperContext(id, childContent2); + var tagHelperOutput2 = new TagHelperOutput("cache", new Dictionary { { "attr", "value" } }) + { + PreContent = "", + PostContent = "" + }; + var cacheTagHelper2 = new CacheTagHelper + { + ViewContext = GetViewContext(), + MemoryCache = cache, + ExpiresSliding = TimeSpan.FromSeconds(30) + }; + + // Act - 2 + await cacheTagHelper2.ProcessAsync(tagHelperContext2, tagHelperOutput2); + + // Assert - 2 + Assert.Null(tagHelperOutput2.PreContent); + Assert.Null(tagHelperOutput2.PostContent); + Assert.True(tagHelperOutput2.ContentSet); + Assert.Equal(childContent2, tagHelperOutput2.Content); + } + + private static ViewContext GetViewContext() + { + var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); + return new ViewContext(actionContext, + Mock.Of(), + new ViewDataDictionary(new EmptyModelMetadataProvider()), + TextWriter.Null); + } + + private static TagHelperContext GetTagHelperContext(string id = "testid", + string childContent = "some child content") + { + return new TagHelperContext(new Dictionary(), + id, + () => Task.FromResult(childContent)); + } + + private static string GetHashedBytes(string input) + { + using (var sha = SHA256.Create()) + { + var contentBytes = Encoding.UTF8.GetBytes(input); + var hashedBytes = sha.ComputeHash(contentBytes); + return Convert.ToBase64String(hashedBytes); + } + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Components/SplashViewComponent.cs b/test/WebSites/MvcTagHelpersWebSite/Components/SplashViewComponent.cs new file mode 100644 index 0000000000..de643189b6 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Components/SplashViewComponent.cs @@ -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. + +using Microsoft.AspNet.Mvc; + +namespace MvcSample.Web.Components +{ + public class SplashViewComponent : ViewComponent + { + public ViewViewComponentResult Invoke() + { + var region = (string)ViewData["Locale"]; + var model = region == "North" ? "NorthWest Store": + "Nationwide Store"; + + return View(model: model); + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs b/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs new file mode 100644 index 0000000000..1f83b6e0f4 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs @@ -0,0 +1,65 @@ +// 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.Security.Claims; +using Microsoft.AspNet.Mvc; + +namespace MvcTagHelpersWebSite.Controllers +{ + public class Catalog_CacheTagHelperController : Controller + { + [HttpGet("/catalog")] + public ViewResult Splash(int categoryId, int correlationId, [FromHeader]string locale) + { + var category = categoryId == 1 ? "Laptops" : "Phones"; + ViewData["Category"] = category; + ViewData["Locale"] = locale; + ViewData["CorrelationId"] = correlationId; + + return View(); + } + + [HttpGet("/catalog/{id:int}")] + public ViewResult Details(int id) + { + ViewData["ProductId"] = id; + return View(); + } + + [HttpGet("/catalog/cart")] + public ViewResult ShoppingCart(int correlationId) + { + ViewData["CorrelationId"] = correlationId; + return View(); + } + + [HttpGet("/catalog/{region}/confirm-payment")] + public ViewResult GuestConfirmPayment(string region, int confirmationId = 0) + { + ViewData["Message"] = "Welcome Guest. Your confirmation id is " + confirmationId; + ViewData["Region"] = region; + return View("ConfirmPayment"); + } + + [HttpGet("/catalog/{region}/{section}/confirm-payment")] + public ViewResult ConfirmPayment(string region, string section, int confirmationId) + { + var message = "Welcome " + section + " member. Your confirmation id is " + confirmationId; + ViewData["Message"] = message; + ViewData["Region"] = region; + + return View(); + } + + [HttpGet("/catalog/past-purchases/{id}")] + public ViewResult PastPurchases(string id, int correlationId) + { + var identity = new ClaimsIdentity(); + identity.AddClaim(new Claim(ClaimsIdentity.DefaultNameClaimType, id)); + + Context.User = new ClaimsPrincipal(identity); + ViewData["CorrelationId"] = correlationId; + return View(); + } + } +} diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ConfirmPayment.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ConfirmPayment.cshtml new file mode 100644 index 0000000000..13ff1097b4 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ConfirmPayment.cshtml @@ -0,0 +1,3 @@ + +@ViewBag.Message. (Region @ViewBag.Region) + \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Details.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Details.cshtml new file mode 100644 index 0000000000..6f98ef7171 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Details.cshtml @@ -0,0 +1,4 @@ +@using Microsoft.Framework.Cache.Memory + +Cached content for @ViewBag.ProductId + diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/PastPurchases.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/PastPurchases.cshtml new file mode 100644 index 0000000000..f389fb77f0 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/PastPurchases.cshtml @@ -0,0 +1,3 @@ + +Past purchases for user @Context.User.Identity.Name (@ViewBag.CorrelationId) + \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ShoppingCart.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ShoppingCart.cshtml new file mode 100644 index 0000000000..34e09985cc --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ShoppingCart.cshtml @@ -0,0 +1,3 @@ + +Cart content for @ViewBag.CorrelationId + \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Splash.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Splash.cshtml new file mode 100644 index 0000000000..f978286a7c --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/Splash.cshtml @@ -0,0 +1,8 @@ +

Category: @ViewBag.Category

+

Region: @ViewBag.Locale

+ +

Cached content

+ @await Component.InvokeAsync("Splash") + @await Html.PartialAsync("_SplashPartial") +
CorrelationId in Splash: @ViewBag.CorrelationId
+
diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/_SplashPartial.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/_SplashPartial.cshtml new file mode 100644 index 0000000000..58b292f16f --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/_SplashPartial.cshtml @@ -0,0 +1,5 @@ +Listing items + +Cached Content for @ViewBag.Category +
CorrelationId in Partial: @ViewBag.CorrelationId
+
\ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/_ViewStart.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/_ViewStart.cshtml new file mode 100644 index 0000000000..8febe27b2c --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/_ViewStart.cshtml @@ -0,0 +1 @@ +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers" \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Splash/Default.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Splash/Default.cshtml new file mode 100644 index 0000000000..e62c9a6450 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Splash/Default.cshtml @@ -0,0 +1,7 @@ +@addtaghelper "Microsoft.AspNet.Mvc.TagHelpers" +@model string +Locations closest to your locale: + +@Model +
CorrelationId in View Component: @ViewBag.CorrelationId
+
\ No newline at end of file From 12565daf88603501521bf0315617be443fa5f705 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Fri, 16 Jan 2015 17:20:34 -0800 Subject: [PATCH 093/118] Correct `CheckBoxFor` to ignore `ViewData` - #1483 - update tests to match nits: - cover a couple more `CheckBoxFor` test cases - capitalize "CheckBox" consistently --- .../Rendering/Html/DefaultHtmlGenerator.cs | 3 +- .../Rendering/HtmlHelperCheckboxTest.cs | 71 ++++++++++++++----- 2 files changed, 55 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs index b666940b67..5f3c565b9a 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Rendering/Html/DefaultHtmlGenerator.cs @@ -117,13 +117,14 @@ namespace Microsoft.AspNet.Mvc.Rendering htmlAttributeDictionary.Remove("checked"); } + // Use ViewData only in CheckBox case (metadata null) and when the user didn't pass an isChecked value. return GenerateInput( viewContext, InputType.CheckBox, metadata, name, value: "true", - useViewData: !explicitValue, + useViewData: (metadata == null && !explicitValue), isChecked: isChecked ?? false, setId: true, isExplicitValue: false, diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperCheckboxTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperCheckboxTest.cs index be8ea71cc8..2ce35c5b1d 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperCheckboxTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Rendering/HtmlHelperCheckboxTest.cs @@ -11,7 +11,7 @@ using Xunit; namespace Microsoft.AspNet.Mvc.Rendering { - public class HtmlHelperCheckboxTest + public class HtmlHelperCheckBoxTest { [Fact] public void CheckBoxOverridesCalculatedValuesWithValuesFromHtmlAttributes() @@ -214,16 +214,46 @@ namespace Microsoft.AspNet.Mvc.Rendering } [Fact] - public void CheckBoxForWithInvalidBooleanThrows() + public void CheckBoxForWithNullContainer_TreatsBooleanAsFalse() { // Arrange - var expected = "String was not recognized as a valid Boolean."; - var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData()); + var expected = @"" + + @""; + var viewData = GetTestModelViewData(); + var helper = DefaultTemplatesUtilities.GetHtmlHelper(viewData); + var valueProviderResult = new ValueProviderResult("false", "false", CultureInfo.InvariantCulture); + viewData.ModelState.SetModelValue("Property1", valueProviderResult); - // Act & Assert - // "Property2" in ViewData isn't a valid boolean - var ex = Assert.Throws(() => helper.CheckBoxFor(m => m.Property2)); - Assert.Equal(expected, ex.Message); + // Act + var html = helper.CheckBoxFor(m => m.Property1); + + // Assert + Assert.Equal(expected, html.ToString()); + } + + [Theory] + [InlineData(false, "")] + [InlineData(true, "checked=\"checked\" ")] + public void CheckBoxForWithNonNullContainer_UsesPropertyValue(bool value, string expectedChecked) + { + // Arrange + var expected = @"" + + @""; + expected = string.Format(expected, expectedChecked); + + var viewData = GetTestModelViewData(); + viewData.Model = new TestModel + { + Property1 = value, + }; + + var helper = DefaultTemplatesUtilities.GetHtmlHelper(viewData); + + // Act + var html = helper.CheckBoxFor(m => m.Property1); + + // Assert + Assert.Equal(expected, html.ToString()); } [Fact] @@ -262,15 +292,20 @@ namespace Microsoft.AspNet.Mvc.Rendering Assert.Equal(expected, html.ToString()); } - [Fact] - public void CheckBoxForUsesModelStateAttemptedValue() + [Theory] + [InlineData("false", "")] + [InlineData("true", "checked=\"checked\" ")] + public void CheckBoxFor_UsesModelStateAttemptedValue(string attemptedValue, string expectedChecked) { // Arrange - var expected = @"" + + var expected = @"" + @""; + expected = string.Format(expected, expectedChecked); + var viewData = GetTestModelViewData(); var helper = DefaultTemplatesUtilities.GetHtmlHelper(viewData); - var valueProviderResult = new ValueProviderResult("false", "false", CultureInfo.InvariantCulture); + var valueProviderResult = + new ValueProviderResult(attemptedValue, attemptedValue, CultureInfo.InvariantCulture); viewData.ModelState.SetModelValue("Property1", valueProviderResult); // Act @@ -281,10 +316,10 @@ namespace Microsoft.AspNet.Mvc.Rendering } [Fact] - public void CheckBoxForWithObjectAttributeWithUnderscores() + public void CheckBoxFor_WithObjectAttribute_MapsUnderscoresInNamesToDashes() { // Arrange - var expected = @""; var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData()); @@ -298,10 +333,10 @@ namespace Microsoft.AspNet.Mvc.Rendering } [Fact] - public void CheckBoxForWithAttributeDictionary() + public void CheckBoxForWith_AttributeDictionary_GeneratesExpectedAttributes() { // Arrange - var expected = @""; var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetTestModelViewData()); @@ -333,10 +368,10 @@ namespace Microsoft.AspNet.Mvc.Rendering } [Fact] - public void CheckboxForWithComplexExpressionsUsesValuesFromViewDataDictionary() + public void CheckBoxFor_WithComplexExpressions_DoesNotUseValuesFromViewDataDictionary() { // Arrange - var expected = @""; var helper = DefaultTemplatesUtilities.GetHtmlHelper(GetModelWithValidationViewData()); From e41e5066f991c8ca74f8d6d87d2f41590784b634 Mon Sep 17 00:00:00 2001 From: Kirthi Krishnamraju Date: Tue, 13 Jan 2015 16:30:06 -0800 Subject: [PATCH 094/118] Added support for TryValidateModel and its corresponding tests --- src/Microsoft.AspNet.Mvc.Core/Controller.cs | 53 +++++++++- .../ControllerTests.cs | 100 ++++++++++++++++++ .../TryValidateModelTest.cs | 73 +++++++++++++ .../Controllers/TryValidateModelController.cs | 64 +++++++++++ .../FormatterWebSite/Models/AdminValidator.cs | 22 ++++ .../FormatterWebSite/Models/Administrator.cs | 13 +++ 6 files changed, 324 insertions(+), 1 deletion(-) create mode 100644 test/Microsoft.AspNet.Mvc.FunctionalTests/TryValidateModelTest.cs create mode 100644 test/WebSites/FormatterWebSite/Controllers/TryValidateModelController.cs create mode 100644 test/WebSites/FormatterWebSite/Models/AdminValidator.cs create mode 100644 test/WebSites/FormatterWebSite/Models/Administrator.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index e0fd035e68..72661425a0 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -1030,6 +1030,57 @@ namespace Microsoft.AspNet.Mvc predicate); } + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// true if the is valid; false otherwise. + [NonAction] + public virtual bool TryValidateModel([NotNull] object model) + { + return TryValidateModel(model, prefix: null); + } + + /// + /// Validates the specified instance. + /// + /// The model to validate. + /// The key to use when looking up information in . + /// + /// true if the is valid;false otherwise. + [NonAction] + public virtual bool TryValidateModel([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() { Dispose(disposing: true); @@ -1040,4 +1091,4 @@ namespace Microsoft.AspNet.Mvc { } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs index 0fbdee98f4..23e0ac6f1e 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerTests.cs @@ -1237,6 +1237,101 @@ namespace Microsoft.AspNet.Mvc.Test Assert.True(controller.DisposeCalled); } + [Fact] + public void TryValidateModelWithValidModel_ReturnsTrue() + { + // Arrange + var binder = new Mock(); + var controller = GetController(binder.Object, provider: null); + controller.BindingContext.ValidatorProvider = Mock.Of(); + var model = new TryValidateModelModel(); + + // Act + var result = controller.TryValidateModel(model); + + // Assert + Assert.True(result); + Assert.True(controller.ModelState.IsValid); + } + + [Fact] + public void TryValidateModelWithInvalidModelWithPrefix_ReturnsFalse() + { + // Arrange + var model = new TryValidateModelModel(); + var validationResult = new [] + { + new ModelValidationResult(string.Empty, "Out of range!") + }; + + var validator1 = new Mock(); + + validator1.Setup(v => v.Validate(It.IsAny())) + .Returns(validationResult); + + var provider = new Mock(); + provider.Setup(v => v.GetValidators(It.IsAny())) + .Returns(new[] { validator1.Object }); + + var binder = new Mock(); + var controller = GetController(binder.Object, provider: null); + controller.BindingContext.ValidatorProvider = provider.Object; + + // Act + var result = controller.TryValidateModel(model, "Prefix"); + + // Assert + Assert.False(result); + Assert.Equal(1, controller.ModelState.Count); + var error = Assert.Single(controller.ModelState["Prefix.IntegerProperty"].Errors); + Assert.Equal("Out of range!", error.ErrorMessage); + } + + [Fact] + public void TryValidateModelWithInvalidModelNoPrefix_ReturnsFalse() + { + // Arrange + var model = new TryValidateModelModel(); + var validationResult = new[] + { + new ModelValidationResult(string.Empty, "Out of range!") + }; + + var validator1 = new Mock(); + + validator1.Setup(v => v.Validate(It.IsAny())) + .Returns(validationResult); + + var provider = new Mock(); + provider.Setup(v => v.GetValidators(It.IsAny())) + .Returns(new[] { validator1.Object }); + + var binder = new Mock(); + var controller = GetController(binder.Object, provider: null); + controller.BindingContext.ValidatorProvider = provider.Object; + + // Act + var result = controller.TryValidateModel(model); + + // Assert + Assert.False(result); + Assert.Equal(1, controller.ModelState.Count); + var error = Assert.Single(controller.ModelState["IntegerProperty"].Errors); + Assert.Equal("Out of range!", error.ErrorMessage); + } + + [Fact] + public void TryValidateModelEmptyBindingContextThrowsException() + { + // Arrange + var controller = new Controller(); + var model = new TryValidateModelModel(); + + // Act & Assert + var exception = Assert.Throws(() => controller.TryValidateModel(model)); + Assert.Equal("The 'BindingContext' property of 'Microsoft.AspNet.Mvc.Controller' must not be null.", exception.Message); + } + private static Controller GetController(IModelBinder binder, IValueProvider provider) { var metadataProvider = new DataAnnotationsModelMetadataProvider(); @@ -1287,6 +1382,11 @@ namespace Microsoft.AspNet.Mvc.Test public int Zip { get; set; } } + private class TryValidateModelModel + { + public int IntegerProperty { get; set; } + } + private class DisposableController : Controller { public bool DisposeCalled { get; private set; } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/TryValidateModelTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/TryValidateModelTest.cs new file mode 100644 index 0000000000..23d9364d79 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/TryValidateModelTest.cs @@ -0,0 +1,73 @@ +// 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.Net; +using System.Threading.Tasks; +using Microsoft.AspNet.Builder; +using Microsoft.AspNet.TestHost; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNet.Mvc.FunctionalTests +{ + public class TryValidateModelTest + { + private readonly IServiceProvider _services = TestHelper.CreateServices(nameof(FormatterWebSite)); + private readonly Action _app = new FormatterWebSite.Startup().Configure; + + [Fact] + public async Task TryValidateModel_SimpleModelInvalidProperties() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/TryValidateModel/GetInvalidUser"); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + + var json = JsonConvert.DeserializeObject>( + await response.Content.ReadAsStringAsync()); + Assert.Equal("The field Id must be between 1 and 2000.", json["Id"][0]); + Assert.Equal( + "The field Name must be a string or array type with a minimum length of '5'.", json["Name"][0]); + } + + [Fact] + public async Task TryValidateModel_DerivedModelInvalidType() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/TryValidateModel/GetInvalidAdminWithPrefix"); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + + var json = JsonConvert.DeserializeObject>( + await response.Content.ReadAsStringAsync()); + Assert.Equal("AdminAccessCode property does not have the right value", json["admin"][0]); + } + + [Fact] + public async Task TryValidateModel_ValidDerivedModel() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/TryValidateModel/GetValidAdminWithPrefix"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Admin user created successfully", await response.Content.ReadAsStringAsync()); + } + } +} \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Controllers/TryValidateModelController.cs b/test/WebSites/FormatterWebSite/Controllers/TryValidateModelController.cs new file mode 100644 index 0000000000..d041232b94 --- /dev/null +++ b/test/WebSites/FormatterWebSite/Controllers/TryValidateModelController.cs @@ -0,0 +1,64 @@ +// 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 FormatterWebSite +{ + public class TryValidateModelController : Controller + { + [HttpGet] + public IActionResult GetInvalidUser() + { + var user = new User + { + Id = 0, + Name = "x" + }; + + // If ModelState.InValid is false return BadRequestOjectResult; else return empty string. + if (!TryValidateModel(user)) + { + return new BadRequestObjectResult(ModelState); + } + + return Content(string.Empty); + } + + [HttpGet] + public IActionResult GetInvalidAdminWithPrefix() + { + var admin = new Administrator() + { + Id = 1, + Name = "John Doe", + Designation = "Administrator", + AdminAccessCode = 0 + }; + if (!TryValidateModel(admin,"admin")) + { + return new BadRequestObjectResult(ModelState); + } + + return Content(string.Empty); + } + + [HttpGet] + public IActionResult GetValidAdminWithPrefix() + { + var admin = new Administrator() + { + Id = 1, + Name = "John Doe", + Designation = "Administrator", + AdminAccessCode = 1 + }; + if (!TryValidateModel(admin, "admin")) + { + return new BadRequestObjectResult(ModelState); + } + + return Content("Admin user created successfully"); + } + } +} diff --git a/test/WebSites/FormatterWebSite/Models/AdminValidator.cs b/test/WebSites/FormatterWebSite/Models/AdminValidator.cs new file mode 100644 index 0000000000..03ebcf4b4a --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/AdminValidator.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 System.ComponentModel.DataAnnotations; + +namespace FormatterWebSite +{ + public class AdminValidator : ValidationAttribute + { + protected override ValidationResult IsValid(object value, ValidationContext validationContext) + { + var admin = (Administrator)value; + + if (admin.AdminAccessCode != 1) + { + return new ValidationResult ("AdminAccessCode property does not have the right value"); + } + + return null; + } + } +} \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Models/Administrator.cs b/test/WebSites/FormatterWebSite/Models/Administrator.cs new file mode 100644 index 0000000000..ce82dd9ded --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/Administrator.cs @@ -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. + +using System; + +namespace FormatterWebSite +{ + [AdminValidator] + public class Administrator : User + { + public int AdminAccessCode { get; set; } + } +} \ No newline at end of file From 692a07240c34ee12c317eddb7cc28d744818b02d Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 16 Jan 2015 01:10:09 -0800 Subject: [PATCH 095/118] Some cleanup of ActionResults - #657 In general all properties are get/set so filters can change them. - some validate for not-null - where we use services it's get/set also Services are resolved in the Execute method if not provided. A few more ActionResults that return a body have the ability to set a status code now (optional). --- .../ActionResults/ContentResult.cs | 10 +++ .../ActionResults/CreatedAtActionResult.cs | 12 ++-- .../ActionResults/CreatedAtRouteResult.cs | 8 +-- .../ActionResults/CreatedResult.cs | 21 +++++- .../ActionResults/FileContentResult.cs | 33 ++++++++- .../ActionResults/FilePathResult.cs | 40 +++++++---- .../ActionResults/FileResult.cs | 12 ++-- .../ActionResults/FileStreamResult.cs | 33 ++++++++- .../ActionResults/JsonResult.cs | 15 +++++ .../ActionResults/NoContentResult.cs | 16 +---- .../ActionResults/ObjectResult.cs | 14 ++-- .../ActionResults/PartialViewResult.cs | 10 +++ .../ActionResults/RedirectResult.cs | 38 ++++++++--- .../ActionResults/RedirectToActionResult.cs | 35 ++++++---- .../ActionResults/RedirectToRouteResult.cs | 39 ++++++----- .../ActionResults/ViewResult.cs | 10 +++ src/Microsoft.AspNet.Mvc.Core/Controller.cs | 26 +++++-- .../ApiController.cs | 5 +- .../ActionResults/FilePathResultTest.cs | 67 ++++++++++++++----- .../RedirectToActionResultTest.cs | 14 ++-- .../RedirectToRouteResultTest.cs | 19 +++--- .../ControllerUnitTestabilityTests.cs | 11 ++- 22 files changed, 356 insertions(+), 132 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ContentResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ContentResult.cs index 5a3dd9c21a..9da4c0c7b1 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ContentResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ContentResult.cs @@ -16,6 +16,11 @@ namespace Microsoft.AspNet.Mvc public string ContentType { get; set; } + /// + /// Gets or sets the HTTP status code. + /// + public int? StatusCode { get; set; } + public override async Task ExecuteResultAsync([NotNull] ActionContext context) { var response = context.HttpContext.Response; @@ -33,6 +38,11 @@ namespace Microsoft.AspNet.Mvc contentTypeHeader.Encoding = ContentEncoding ?? Encodings.UTF8EncodingWithoutBOM; response.ContentType = contentTypeHeader.ToString(); + if (StatusCode != null) + { + response.StatusCode = StatusCode.Value; + } + if (Content != null) { await response.WriteAsync(Content, contentTypeHeader.Encoding); diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs index df74e54a34..279b890ad0 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtActionResult.cs @@ -39,19 +39,19 @@ namespace Microsoft.AspNet.Mvc public IUrlHelper UrlHelper { get; set; } /// - /// Gets the name of the action to use for generating the URL. + /// Gets or sets the name of the action to use for generating the URL. /// - public string ActionName { get; private set; } + public string ActionName { get; set; } /// - /// Gets the name of the controller to use for generating the URL. + /// Gets or sets the name of the controller to use for generating the URL. /// - public string ControllerName { get; private set; } + public string ControllerName { get; set; } /// - /// Gets the route data to use for generating the URL. + /// Gets or sets the route data to use for generating the URL. /// - public IDictionary RouteValues { get; private set; } + public IDictionary RouteValues { get; set; } /// protected override void OnFormatting([NotNull] ActionContext context) diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs index d1e1989239..ba0ea40f50 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedAtRouteResult.cs @@ -47,14 +47,14 @@ namespace Microsoft.AspNet.Mvc public IUrlHelper UrlHelper { get; set; } /// - /// Gets the name of the route to use for generating the URL. + /// Gets or sets the name of the route to use for generating the URL. /// - public string RouteName { get; private set; } + public string RouteName { get; set; } /// - /// Gets the route data to use for generating the URL. + /// Gets or sets the route data to use for generating the URL. /// - public IDictionary RouteValues { get; private set; } + public IDictionary RouteValues { get; set; } /// protected override void OnFormatting([NotNull] ActionContext context) diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedResult.cs index 6061f97d9d..006e0d1b77 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/CreatedResult.cs @@ -10,6 +10,8 @@ namespace Microsoft.AspNet.Mvc /// public class CreatedResult : ObjectResult { + private string _location; + /// /// Initializes a new instance of the class with the values /// provided. @@ -24,9 +26,24 @@ namespace Microsoft.AspNet.Mvc } /// - /// Gets the location at which the content has been created. + /// Gets or sets the location at which the content has been created. /// - public string Location { get; private set; } + public string Location + { + get + { + return _location; + } + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + _location = value; + } + } /// protected override void OnFormatting([NotNull] ActionContext context) diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileContentResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileContentResult.cs index 9fe50228ee..6e3bde543d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileContentResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileContentResult.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Http; @@ -13,6 +14,19 @@ namespace Microsoft.AspNet.Mvc /// public class FileContentResult : FileResult { + private byte[] _fileContents; + + /// + /// Creates a new instance with + /// the provided . + /// + /// The bytes that represent the file contents. + public FileContentResult([NotNull] byte[] fileContents) + : base(contentType: null) + { + FileContents = fileContents; + } + /// /// Creates a new instance with /// the provided and the @@ -27,9 +41,24 @@ namespace Microsoft.AspNet.Mvc } /// - /// Gets the file contents. + /// Gets or sets the file contents. /// - public byte[] FileContents { get; private set; } + public byte[] FileContents + { + get + { + return _fileContents; + } + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + _fileContents = value; + } + } /// protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation) diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs index 9eb2b9610f..56fbba95ab 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs @@ -15,7 +15,7 @@ using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc { /// - /// Represents an that when executed will + /// An that when executed will /// write a file from disk to the response using mechanisms provided /// by the host. /// @@ -23,16 +23,17 @@ namespace Microsoft.AspNet.Mvc { private const int DefaultBufferSize = 0x1000; + private string _fileName; + /// /// Creates a new instance with - /// the provided and the - /// provided . + /// the provided /// /// The path to the file. The path must be an absolute /// path. Relative and virtual paths are not supported. /// The Content-Type header of the response. - public FilePathResult([NotNull] string fileName, [NotNull] string contentType) - : base(contentType) + public FilePathResult([NotNull] string fileName) + : base(contentType: null) { FileName = fileName; } @@ -45,25 +46,36 @@ namespace Microsoft.AspNet.Mvc /// The path to the file. The path must be an absolute /// path. Relative and virtual paths are not supported. /// The Content-Type header of the response. - public FilePathResult( - [NotNull] string fileName, - [NotNull] string contentType, - [NotNull] IFileSystem fileSystem) + public FilePathResult([NotNull] string fileName, string contentType) : base(contentType) { FileName = fileName; - FileSystem = fileSystem; } /// - /// 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. /// - public string FileName { get; private set; } + public string FileName + { + get + { + return _fileName; + } + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + _fileName = value; + } + } /// - /// Gets the used to resolve paths. + /// Gets or sets the used to resolve paths. /// - public IFileSystem FileSystem { get; private set; } + public IFileSystem FileSystem { get; set; } /// protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation) diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileResult.cs index 15a6af9fb2..a8f3b3ed24 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileResult.cs @@ -23,15 +23,15 @@ namespace Microsoft.AspNet.Mvc /// the provided . /// /// The Content-Type header of the response. - protected FileResult([NotNull] string contentType) + protected FileResult(string contentType) { ContentType = contentType; } /// - /// 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. /// - public string ContentType { get; private set; } + public string ContentType { get; set; } /// /// Gets the file name that will be used in the Content-Disposition header of the response. @@ -46,7 +46,11 @@ namespace Microsoft.AspNet.Mvc public override Task ExecuteResultAsync([NotNull] ActionContext context) { var response = context.HttpContext.Response; - response.ContentType = ContentType; + + if (ContentType != null) + { + response.ContentType = ContentType; + } if (!string.IsNullOrEmpty(FileDownloadName)) { diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileStreamResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileStreamResult.cs index d7e4337790..c9792f7a4b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileStreamResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FileStreamResult.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -17,6 +18,19 @@ namespace Microsoft.AspNet.Mvc // default buffer size as defined in BufferedStream type private const int BufferSize = 0x1000; + private Stream _fileStream; + + /// + /// Creates a new instance with + /// the provided . + /// + /// The stream with the file. + public FileStreamResult([NotNull] Stream fileStream) + : base(contentType: null) + { + FileStream = fileStream; + } + /// /// Creates a new instance with /// the provided and the @@ -31,9 +45,24 @@ namespace Microsoft.AspNet.Mvc } /// - /// 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. /// - public Stream FileStream { get; private set; } + public Stream FileStream + { + get + { + return _fileStream; + } + set + { + if (value == null) + { + throw new ArgumentNullException("value"); + } + + _fileStream = value; + } + } /// protected async override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation) diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs index 2fb77722b4..3816e2332c 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/JsonResult.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.Framework.DependencyInjection; @@ -55,6 +56,11 @@ namespace Microsoft.AspNet.Mvc /// public IOutputFormatter Formatter { get; set; } + /// + /// Gets or sets the HTTP status code. + /// + public int? StatusCode { get; set; } + /// /// Gets or sets the value to be formatted. /// @@ -86,7 +92,16 @@ namespace Microsoft.AspNet.Mvc Object = Value, }; + // JsonResult expects to always find a formatter, in contrast with ObjectResult, which might return + // a 406. var formatter = SelectFormatter(objectResult, formatterContext); + Debug.Assert(formatter != null); + + if (StatusCode != null) + { + context.HttpContext.Response.StatusCode = StatusCode.Value; + } + await formatter.WriteAsync(formatterContext); } diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/NoContentResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/NoContentResult.cs index 59ee0f6edd..2efda64bfe 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/NoContentResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/NoContentResult.cs @@ -1,23 +1,13 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -#if ASPNET50 -using System.Net; -#endif - namespace Microsoft.AspNet.Mvc { - public class NoContentResult : ActionResult + public class NoContentResult : HttpStatusCodeResult { - public override void ExecuteResult([NotNull] ActionContext context) + public NoContentResult() + : base(204) { - var response = context.HttpContext.Response; - -#if ASPNET50 - response.StatusCode = (int)HttpStatusCode.NoContent; -#else - response.StatusCode = 204; -#endif } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs index 1af7787ca6..71dc28ffd4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ObjectResult.cs @@ -14,6 +14,13 @@ namespace Microsoft.AspNet.Mvc { public class ObjectResult : ActionResult { + public ObjectResult(object value) + { + Value = value; + Formatters = new List(); + ContentTypes = new List(); + } + public object Value { get; set; } public IList Formatters { get; set; } @@ -27,13 +34,6 @@ namespace Microsoft.AspNet.Mvc /// public int? StatusCode { get; set; } - public ObjectResult(object value) - { - Value = value; - Formatters = new List(); - ContentTypes = new List(); - } - public override async Task ExecuteResultAsync(ActionContext context) { var formatters = GetDefaultFormatters(context); diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/PartialViewResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/PartialViewResult.cs index efcac52297..b6b9d345f2 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/PartialViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/PartialViewResult.cs @@ -13,6 +13,11 @@ namespace Microsoft.AspNet.Mvc /// public class PartialViewResult : ActionResult { + /// + /// Gets or sets the HTTP status code. + /// + public int? StatusCode { get; set; } + /// /// Gets or sets the name of the partial view to render. /// @@ -44,6 +49,11 @@ namespace Microsoft.AspNet.Mvc .EnsureSuccessful() .View; + if (StatusCode != null) + { + context.HttpContext.Response.StatusCode = StatusCode.Value; + } + using (view as IDisposable) { await ViewExecutor.ExecuteAsync(view, context, ViewData, contentType: null); diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectResult.cs index b957da1af8..11c6616599 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectResult.cs @@ -9,12 +9,14 @@ namespace Microsoft.AspNet.Mvc { public class RedirectResult : ActionResult { - public RedirectResult(string url) + private string _url; + + public RedirectResult([NotNull] string url) : this(url, permanent: false) { } - public RedirectResult(string url, bool permanent) + public RedirectResult([NotNull] string url, bool permanent) { if (string.IsNullOrEmpty(url)) { @@ -25,18 +27,33 @@ namespace Microsoft.AspNet.Mvc Url = url; } - public bool Permanent { get; private set; } + public bool Permanent { get; set; } - public string Url { get; private set; } + public string Url + { + get + { + return _url; + } + set + { + if (string.IsNullOrEmpty(value)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, "value"); + } + + _url = value; + } + } + + public IUrlHelper UrlHelper { get; set; } public override void ExecuteResult([NotNull] ActionContext context) { - var destinationUrl = Url; - var urlHelper = context.HttpContext - .RequestServices - .GetRequiredService(); + var urlHelper = GetUrlHelper(context); // IsLocalUrl is called to handle Urls starting with '~/'. + var destinationUrl = Url; if (urlHelper.IsLocalUrl(destinationUrl)) { destinationUrl = urlHelper.Content(Url); @@ -44,5 +61,10 @@ namespace Microsoft.AspNet.Mvc context.HttpContext.Response.Redirect(destinationUrl, Permanent); } + + private IUrlHelper GetUrlHelper(ActionContext context) + { + return UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService(); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToActionResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToActionResult.cs index 7798a220bd..8dd00ef26e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToActionResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToActionResult.cs @@ -4,41 +4,47 @@ using System; using System.Collections.Generic; using Microsoft.AspNet.Mvc.Core; +using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc { public class RedirectToActionResult : ActionResult { - public RedirectToActionResult([NotNull] IUrlHelper urlHelper, string actionName, - string controllerName, IDictionary routeValues) - : this(urlHelper, actionName, controllerName, routeValues, permanent: false) + public RedirectToActionResult( + string actionName, + string controllerName, + IDictionary routeValues) + : this(actionName, controllerName, routeValues, permanent: false) { } - public RedirectToActionResult([NotNull] IUrlHelper urlHelper, string actionName, - string controllerName, IDictionary routeValues, bool permanent) + public RedirectToActionResult( + string actionName, + string controllerName, + IDictionary routeValues, + bool permanent) { - UrlHelper = urlHelper; ActionName = actionName; ControllerName = controllerName; RouteValues = routeValues; Permanent = permanent; } - public IUrlHelper UrlHelper { get; private set; } + public IUrlHelper UrlHelper { get; set; } - public string ActionName { get; private set; } + public string ActionName { get; set; } - public string ControllerName { get; private set; } + public string ControllerName { get; set; } - public IDictionary RouteValues { get; private set; } + public IDictionary RouteValues { get; set; } - public bool Permanent { get; private set; } + public bool Permanent { get; set; } public override void ExecuteResult([NotNull] ActionContext context) { - var destinationUrl = UrlHelper.Action(ActionName, ControllerName, RouteValues); + var urlHelper = GetUrlHelper(context); + var destinationUrl = urlHelper.Action(ActionName, ControllerName, RouteValues); if (string.IsNullOrEmpty(destinationUrl)) { throw new InvalidOperationException(Resources.NoRoutesMatched); @@ -46,5 +52,10 @@ namespace Microsoft.AspNet.Mvc context.HttpContext.Response.Redirect(destinationUrl, Permanent); } + + private IUrlHelper GetUrlHelper(ActionContext context) + { + return UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService(); + } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToRouteResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToRouteResult.cs index 1186e5a0ac..3d571bf3a0 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToRouteResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/RedirectToRouteResult.cs @@ -4,47 +4,47 @@ using System; using System.Collections.Generic; using Microsoft.AspNet.Mvc.Core; +using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc { public class RedirectToRouteResult : ActionResult { - public RedirectToRouteResult([NotNull] IUrlHelper urlHelper, - object routeValues) - : this(urlHelper, routeName: null, routeValues: routeValues) + public RedirectToRouteResult(object routeValues) + : this(routeName: null, routeValues: routeValues) { } - public RedirectToRouteResult([NotNull] IUrlHelper urlHelper, - string routeName, - object routeValues) - : this(urlHelper, routeName, routeValues, permanent: false) + public RedirectToRouteResult( + string routeName, + object routeValues) + : this(routeName, routeValues, permanent: false) { } - public RedirectToRouteResult([NotNull] IUrlHelper urlHelper, - string routeName, - object routeValues, - bool permanent) + public RedirectToRouteResult( + string routeName, + object routeValues, + bool permanent) { - UrlHelper = urlHelper; RouteName = routeName; RouteValues = TypeHelper.ObjectToDictionary(routeValues); Permanent = permanent; } - public IUrlHelper UrlHelper { get; private set; } + public IUrlHelper UrlHelper { get; set; } - public string RouteName { get; private set; } + public string RouteName { get; set; } - public IDictionary RouteValues { get; private set; } + public IDictionary RouteValues { get; set; } - public bool Permanent { get; private set; } + public bool Permanent { get; set; } public override void ExecuteResult([NotNull] ActionContext context) { - var destinationUrl = UrlHelper.RouteUrl(RouteValues); + var urlHelper = GetUrlHelper(context); + var destinationUrl = urlHelper.RouteUrl(RouteValues); if (string.IsNullOrEmpty(destinationUrl)) { throw new InvalidOperationException(Resources.NoRoutesMatched); @@ -52,5 +52,10 @@ namespace Microsoft.AspNet.Mvc context.HttpContext.Response.Redirect(destinationUrl, Permanent); } + + private IUrlHelper GetUrlHelper(ActionContext context) + { + return UrlHelper ?? context.HttpContext.RequestServices.GetRequiredService(); + } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs index 45454630f8..314fd6495e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/ViewResult.cs @@ -13,6 +13,11 @@ namespace Microsoft.AspNet.Mvc /// public class ViewResult : ActionResult { + /// + /// Gets or sets the HTTP status code. + /// + public int? StatusCode { get; set; } + /// /// Gets or sets the name of the view to render. /// @@ -44,6 +49,11 @@ namespace Microsoft.AspNet.Mvc .EnsureSuccessful() .View; + if (StatusCode != null) + { + context.HttpContext.Response.StatusCode = StatusCode.Value; + } + using (view as IDisposable) { await ViewExecutor.ExecuteAsync(view, context, ViewData, contentType: null); diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index 72661425a0..e4e4b51226 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -398,8 +398,10 @@ namespace Microsoft.AspNet.Mvc public virtual RedirectToActionResult RedirectToAction(string actionName, string controllerName, object routeValues) { - return new RedirectToActionResult(Url, actionName, controllerName, - TypeHelper.ObjectToDictionary(routeValues)); + return new RedirectToActionResult(actionName, controllerName, TypeHelper.ObjectToDictionary(routeValues)) + { + UrlHelper = Url, + }; } /// @@ -453,8 +455,14 @@ namespace Microsoft.AspNet.Mvc public virtual RedirectToActionResult RedirectToActionPermanent(string actionName, string controllerName, object routeValues) { - return new RedirectToActionResult(Url, actionName, controllerName, - TypeHelper.ObjectToDictionary(routeValues), permanent: true); + return new RedirectToActionResult( + actionName, + controllerName, + TypeHelper.ObjectToDictionary(routeValues), + permanent: true) + { + UrlHelper = Url, + }; } /// @@ -489,7 +497,10 @@ namespace Microsoft.AspNet.Mvc [NonAction] public virtual RedirectToRouteResult RedirectToRoute(string routeName, object routeValues) { - return new RedirectToRouteResult(Url, routeName, routeValues); + return new RedirectToRouteResult(routeName, routeValues) + { + UrlHelper = Url, + }; } /// @@ -526,7 +537,10 @@ namespace Microsoft.AspNet.Mvc [NonAction] public virtual RedirectToRouteResult RedirectToRoutePermanent(string routeName, object routeValues) { - return new RedirectToRouteResult(Url, routeName, routeValues, permanent: true); + return new RedirectToRouteResult(routeName, routeValues, permanent: true) + { + UrlHelper = Url, + }; } /// diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs index 1e3e594e0a..ac9db2ea00 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/ApiController.cs @@ -359,7 +359,10 @@ namespace System.Web.Http [NonAction] public virtual RedirectToRouteResult RedirectToRoute([NotNull] string routeName, [NotNull] object routeValues) { - return new RedirectToRouteResult(Url, routeName, routeValues); + return new RedirectToRouteResult(routeName, routeValues) + { + UrlHelper = Url, + }; } /// diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs index cd2e131781..187d236012 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs @@ -34,8 +34,11 @@ namespace Microsoft.AspNet.Mvc { // Arrange var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt")); - var fileSystem = new PhysicalFileSystem(Path.GetFullPath(".")); - var result = new FilePathResult(path, "text/plain", fileSystem); + + var result = new FilePathResult(path, "text/plain") + { + FileSystem = new PhysicalFileSystem(Path.GetFullPath(".")), + }; var httpContext = new DefaultHttpContext(); httpContext.Response.Body = new MemoryStream(); @@ -86,8 +89,11 @@ namespace Microsoft.AspNet.Mvc { // Arrange var path = Path.GetFullPath(Path.Combine("TestFiles", "FilePathResultTestFile.txt")); - var fileSystem = new PhysicalFileSystem(Path.GetFullPath(".")); - var result = new FilePathResult(path, "text/plain", fileSystem); + + var result = new FilePathResult(path, "text/plain") + { + FileSystem = new PhysicalFileSystem(Path.GetFullPath(".")), + }; var sendFileMock = new Mock(); sendFileMock @@ -117,8 +123,10 @@ namespace Microsoft.AspNet.Mvc path = path.Replace('/', '\\'); // Point the FileSystemRoot to a subfolder - var fileSystem = new PhysicalFileSystem(Path.GetFullPath("Utils")); - var result = new FilePathResult(path, "text/plain", fileSystem); + var result = new FilePathResult(path, "text/plain") + { + FileSystem = new PhysicalFileSystem(Path.GetFullPath("Utils")), + }; var httpContext = new DefaultHttpContext(); httpContext.Response.Body = new MemoryStream(); @@ -144,8 +152,10 @@ namespace Microsoft.AspNet.Mvc path = path.Replace(@"\", "/"); // Point the FileSystemRoot to a subfolder - var fileSystem = new PhysicalFileSystem(Path.GetFullPath("Utils")); - var result = new FilePathResult(path, "text/plain", fileSystem); + var result = new FilePathResult(path, "text/plain") + { + FileSystem = new PhysicalFileSystem(Path.GetFullPath("Utils")), + }; var httpContext = new DefaultHttpContext(); httpContext.Response.Body = new MemoryStream(); @@ -195,7 +205,10 @@ namespace Microsoft.AspNet.Mvc // Arrange var fileSystem = new PhysicalFileSystem(Path.GetFullPath("./TestFiles")); var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePathToFile)); - var filePathResult = new FilePathResult(path, "text/plain", fileSystem); + var filePathResult = new FilePathResult(path, "text/plain") + { + FileSystem = fileSystem, + }; // Act var result = filePathResult.ResolveFilePath(fileSystem); @@ -213,7 +226,10 @@ namespace Microsoft.AspNet.Mvc // Arrange var fileSystem = new PhysicalFileSystem(Path.GetFullPath("./TestFiles")); var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePathToFile)); - var filePathResult = new FilePathResult(path, "text/plain", fileSystem); + var filePathResult = new FilePathResult(path, "text/plain") + { + FileSystem = fileSystem, + }; // Act var ex = Assert.Throws(() => filePathResult.ResolveFilePath(fileSystem)); @@ -257,7 +273,11 @@ namespace Microsoft.AspNet.Mvc // Point the IFileSystem root to a different subfolder var fileSystem = new PhysicalFileSystem(Path.GetFullPath("./Utils")); - var filePathResult = new FilePathResult(path, "text/plain", fileSystem); + var filePathResult = new FilePathResult(path, "text/plain") + { + FileSystem = fileSystem, + }; + var expectedFileName = path.TrimStart('~').Replace('\\', '/'); var expectedMessage = "Could not find file: " + expectedFileName; @@ -300,7 +320,10 @@ namespace Microsoft.AspNet.Mvc public void NormalizePath_ConvertsBackSlashes_IntoForwardSlashes(string path, string expectedPath) { // Arrange - var fileResult = new FilePathResult(path, "text/plain", Mock.Of()); + var fileResult = new FilePathResult(path, "text/plain") + { + FileSystem = Mock.Of(), + }; // Act var normalizedPath = fileResult.NormalizePath(path); @@ -317,7 +340,10 @@ namespace Microsoft.AspNet.Mvc public void NormalizePath_ConvertsVirtualPaths_IntoRelativePaths(string path, string expectedPath) { // Arrange - var fileResult = new FilePathResult(path, "text/plain", Mock.Of()); + var fileResult = new FilePathResult(path, "text/plain") + { + FileSystem = Mock.Of(), + }; // Act var normalizedPath = fileResult.NormalizePath(path); @@ -334,7 +360,10 @@ namespace Microsoft.AspNet.Mvc public void NormalizePath_DoesNotConvert_InvalidVirtualPathsIntoRelativePaths(string path) { // Arrange - var fileResult = new FilePathResult(path, "text/plain", Mock.Of()); + var fileResult = new FilePathResult(path, "text/plain") + { + FileSystem = Mock.Of(), + }; // Act var normalizedPath = fileResult.NormalizePath(path); @@ -351,7 +380,10 @@ namespace Microsoft.AspNet.Mvc public void IsPathRooted_ReturnsTrue_ForAbsolutePaths(string path) { // Arrange - var fileResult = new FilePathResult(path, "text/plain", Mock.Of()); + var fileResult = new FilePathResult(path, "text/plain") + { + FileSystem = Mock.Of(), + }; // Act var isRooted = fileResult.IsPathRooted(path); @@ -393,7 +425,10 @@ namespace Microsoft.AspNet.Mvc public void IsPathRooted_ReturnsFalse_ForRelativePaths(string path) { // Arrange - var fileResult = new FilePathResult(path, "text/plain", Mock.Of()); + var fileResult = new FilePathResult(path, "text/plain") + { + FileSystem = Mock.Of(), + }; // Act var isRooted = fileResult.IsPathRooted(path); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToActionResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToActionResultTest.cs index 4dd5e8b509..903566da9d 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToActionResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToActionResultTest.cs @@ -25,8 +25,11 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); - IUrlHelper urlHelper = GetMockUrlHelper(expectedUrl); - RedirectToActionResult result = new RedirectToActionResult(urlHelper, "SampleAction", null, null); + var urlHelper = GetMockUrlHelper(expectedUrl); + var result = new RedirectToActionResult("SampleAction", null, null) + { + UrlHelper = urlHelper, + }; // Act await result.ExecuteResultAsync(actionContext); @@ -48,8 +51,11 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults new RouteData(), new ActionDescriptor()); - IUrlHelper urlHelper = GetMockUrlHelper(returnValue: null); - RedirectToActionResult result = new RedirectToActionResult(urlHelper, null, null, null); + var urlHelper = GetMockUrlHelper(returnValue: null); + var result = new RedirectToActionResult(null, null, null) + { + UrlHelper = urlHelper, + }; // Act & Assert ExceptionAssert.ThrowsAsync( diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToRouteResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToRouteResultTest.cs index b2f20b7525..02d08e06e3 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToRouteResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/RedirectToRouteResultTest.cs @@ -27,10 +27,12 @@ namespace Microsoft.AspNet.Mvc.Core var actionContext = new ActionContext(httpContext.Object, new RouteData(), new ActionDescriptor()); - IUrlHelper urlHelper = GetMockUrlHelper(expectedUrl); - RedirectToRouteResult result = new RedirectToRouteResult(urlHelper, - null, - TypeHelper.ObjectToDictionary(values)); + + var urlHelper = GetMockUrlHelper(expectedUrl); + var result = new RedirectToRouteResult(null, TypeHelper.ObjectToDictionary(values)) + { + UrlHelper = urlHelper, + }; // Act await result.ExecuteResultAsync(actionContext); @@ -52,10 +54,11 @@ namespace Microsoft.AspNet.Mvc.Core new RouteData(), new ActionDescriptor()); - IUrlHelper urlHelper = GetMockUrlHelper(returnValue: null); - RedirectToRouteResult result = new RedirectToRouteResult(urlHelper, - null, - new Dictionary()); + var urlHelper = GetMockUrlHelper(returnValue: null); + var result = new RedirectToRouteResult(null, new Dictionary()) + { + UrlHelper = urlHelper, + }; // Act & Assert ExceptionAssert.ThrowsAsync( diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs index ed8cafd014..9840ee7805 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerUnitTestabilityTests.cs @@ -85,7 +85,7 @@ namespace Microsoft.AspNet.Mvc [Theory] [InlineData("/Created_1", "CreatedBody")] - [InlineData(null, null)] + [InlineData("/Created_2", null)] public void ControllerCreated_InvokedInUnitTests(string uri, string content) { // Arrange @@ -105,7 +105,7 @@ namespace Microsoft.AspNet.Mvc [Theory] [InlineData("CreatedBody", "text/html", "Created.html")] - [InlineData(null, null, null)] + [InlineData("CreatedBody", null, null)] public void ControllerFileContent_InvokedInUnitTests(string content, string contentType, string fileName) { // Arrange @@ -133,7 +133,7 @@ namespace Microsoft.AspNet.Mvc [Theory] [InlineData("CreatedBody", "text/html", "Created.html")] - [InlineData(null, null, null)] + [InlineData("CreatedBody", null, null)] public void ControllerFileStream_InvokedInUnitTests(string content, string contentType, string fileName) { // Arrange @@ -485,14 +485,13 @@ namespace Microsoft.AspNet.Mvc public IActionResult FileContent_Action(string content, string contentType, string fileName) { - var contentArray = string.IsNullOrEmpty(content) ? null : Encoding.UTF8.GetBytes(content); + var contentArray = Encoding.UTF8.GetBytes(content); return File(contentArray, contentType, fileName); } public IActionResult FileStream_Action(string content, string contentType, string fileName) { - var memoryStream = string.IsNullOrEmpty(content) ? null : - new MemoryStream(Encoding.UTF8.GetBytes(content)); + var memoryStream = new MemoryStream(Encoding.UTF8.GetBytes(content)); return File(memoryStream, contentType, fileName); } From 51e7812e7e0e5fc2e4e0d726f5903f9a8259c254 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 15 Jan 2015 18:02:41 -0800 Subject: [PATCH 096/118] Fix for #1722 - FromHeader does not respect default value This change adds support for our three-valued logic to the default value handling part of the MutableObjectModelBinder. The issue is that we want to look up a default value when a 'greedy' model binder returns true but doesn't find a value. We also don't want to call the property setter unless there is: 1). A value from model binding OR 2). A default value --- .../DefaultControllerActionArgumentBinder.cs | 3 +- .../Binders/ComplexModelDtoModelBinder.cs | 5 +- .../Binders/ComplexModelDtoResult.cs | 18 +- .../Binders/MutableObjectModelBinder.cs | 37 +++- .../ControllerActionArgumentBinderTests.cs | 58 ++++++ .../UpdateDealerVehicle_UpdateSuccessful.txt | 2 +- .../ModelBindingFromHeaderTest.cs | 94 +++++++++ .../Binders/ComplexModelDtoResultTest.cs | 6 +- .../Binders/MutableObjectModelBinderTest.cs | 191 ++++++++++++++---- .../Controllers/ActionFilterController.cs | 15 +- .../Controllers/FromHeader_BlogController.cs | 61 ++++++ 11 files changed, 431 insertions(+), 59 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs index fbdc621602..096cebbb27 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerActionArgumentBinder.cs @@ -88,7 +88,8 @@ namespace Microsoft.AspNet.Mvc { var parameterType = parameter.ModelType; var modelBindingContext = GetModelBindingContext(parameter, actionContext, operationBindingContext); - if (await bindingContext.ModelBinder.BindModelAsync(modelBindingContext)) + if (await bindingContext.ModelBinder.BindModelAsync(modelBindingContext) && + modelBindingContext.IsModelSet) { arguments[parameter.PropertyName] = modelBindingContext.Model; } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs index 3a133acbcb..c92fe2540b 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoModelBinder.cs @@ -27,11 +27,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding propertyMetadata); // bind and propagate the values - // If we can't bind, then leave the result missing (don't add a null). + // If we can't bind then leave the result missing (don't add a null). if (await bindingContext.OperationBindingContext.ModelBinder.BindModelAsync(propertyBindingContext)) { - var result = new ComplexModelDtoResult(propertyBindingContext.Model, - propertyBindingContext.ValidationNode); + var result = ComplexModelDtoResult.FromBindingContext(propertyBindingContext); dto.Results[propertyMetadata] = result; } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoResult.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoResult.cs index ac5a2d826a..cd298dd477 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoResult.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/ComplexModelDtoResult.cs @@ -5,15 +5,25 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { public sealed class ComplexModelDtoResult { - public ComplexModelDtoResult(object model, - [NotNull] ModelValidationNode validationNode) + public static ComplexModelDtoResult FromBindingContext([NotNull] ModelBindingContext context) + { + return new ComplexModelDtoResult(context.Model, context.IsModelSet, context.ValidationNode); + } + + public ComplexModelDtoResult( + object model, + bool isModelBound, + [NotNull] ModelValidationNode validationNode) { Model = model; + IsModelBound = isModelBound; ValidationNode = validationNode; } - public object Model { get; private set; } + public bool IsModelBound { get; } - public ModelValidationNode ValidationNode { get; private set; } + public object Model { get; set; } + + public ModelValidationNode ValidationNode { get; set; } } } diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs index 9447deb58d..da70d2b8eb 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/MutableObjectModelBinder.cs @@ -291,10 +291,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding }; } - private static object GetPropertyDefaultValue(PropertyInfo propertyInfo) + private static bool TryGetPropertyDefaultValue(PropertyInfo propertyInfo, out object value) { - var attr = propertyInfo.GetCustomAttribute(); - return (attr != null) ? attr.Value : null; + var attribute = propertyInfo.GetCustomAttribute(); + if (attribute == null) + { + value = null; + return false; + } + else + { + value = attribute.Value; + return true; + } } internal static PropertyValidationInfo GetPropertyValidationInfo(ModelBindingContext bindingContext) @@ -345,7 +354,8 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var validationInfo = GetPropertyValidationInfo(bindingContext); // Eliminate provided properties from requiredProperties; leaving just *missing* required properties. - validationInfo.RequiredProperties.ExceptWith(dto.Results.Select(r => r.Key.PropertyName)); + var boundProperties = dto.Results.Where(p => p.Value.IsModelBound).Select(p => p.Key.PropertyName); + validationInfo.RequiredProperties.ExceptWith(boundProperties); foreach (var missingRequiredProperty in validationInfo.RequiredProperties) { @@ -407,7 +417,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return; } - var value = dtoResult.Model ?? GetPropertyDefaultValue(property); + object value; + var hasDefaultValue = false; + if (dtoResult.IsModelBound) + { + value = dtoResult.Model; + } + else + { + hasDefaultValue = TryGetPropertyDefaultValue(property, out value); + } + propertyMetadata.Model = value; // 'Required' validators need to run first so that we can provide useful error messages if @@ -429,6 +449,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding } } + if (!dtoResult.IsModelBound && !hasDefaultValue) + { + // If we don't have a value, don't set it on the model and trounce a pre-initialized + // value. + return; + } + if (value != null || property.PropertyType.AllowsNullValue()) { try diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs index 38fa995e78..7116349977 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs @@ -183,6 +183,11 @@ namespace Microsoft.AspNet.Mvc.Core.Test var binder = new Mock(); binder .Setup(b => b.BindModelAsync(It.IsAny())) + .Callback(c => + { + // This value won't go into the arguments, because we return false. + c.Model = "Hello"; + }) .Returns(Task.FromResult(result: false)); var actionContext = new ActionContext( @@ -211,6 +216,59 @@ namespace Microsoft.AspNet.Mvc.Core.Test Assert.Empty(result); } + [Fact] + public async Task GetActionArgumentsAsync_DoesNotAddActionArguments_IfBinderDoesNotSetModel() + { + // Arrange + Func method = foo => 1; + var actionDescriptor = new ControllerActionDescriptor + { + MethodInfo = method.Method, + Parameters = new List + { + new ParameterDescriptor + { + Name = "foo", + ParameterType = typeof(object), + } + } + }; + + var binder = new Mock(); + binder + .Setup(b => b.BindModelAsync(It.IsAny())) + .Callback(c => + { + Assert.False(c.IsModelSet); + }) + .Returns(Task.FromResult(result: true)); + + var actionContext = new ActionContext( + new RouteContext(Mock.Of()), + actionDescriptor) + { + Controller = Mock.Of(), + }; + + var actionBindingContext = new ActionBindingContext() + { + ModelBinder = binder.Object, + }; + + var inputFormattersProvider = new Mock(); + inputFormattersProvider + .SetupGet(o => o.InputFormatters) + .Returns(new List()); + + var invoker = new DefaultControllerActionArgumentBinder(new DataAnnotationsModelMetadataProvider()); + + // Act + var result = await invoker.GetActionArgumentsAsync(actionContext, actionBindingContext); + + // Assert + Assert.Empty(result); + } + [Fact] public async Task GetActionArgumentsAsync_AddsActionArguments_IfBinderReturnsTrue() { diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/UpdateDealerVehicle_UpdateSuccessful.txt b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/UpdateDealerVehicle_UpdateSuccessful.txt index c39a47f1f6..d322993b8f 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/UpdateDealerVehicle_UpdateSuccessful.txt +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Compiler/Resources/UpdateDealerVehicle_UpdateSuccessful.txt @@ -20,5 +20,5 @@
-Tracked by +Tracked by default-tracking-id
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromHeaderTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromHeaderTest.cs index 741bb9481d..6c2102562c 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromHeaderTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ModelBindingFromHeaderTest.cs @@ -99,6 +99,36 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Empty(result.ModelStateErrors); } + // There should be no model state error for a top-level object + [Theory] + [InlineData("transactionId1234", "1e331f25-0869-4c87-8a94-64e6e40cb5a0")] + public async Task FromHeader_BindHeader_ToString_OnParameter_NoValues_DefaultValue( + string headerName, + string headerValue) + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage( + HttpMethod.Get, + "http://localhost/Blog/BindToStringParameterDefaultValue"); + // Intentionally not setting a header value + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("default-value", result.HeaderValue); + Assert.Null(result.HeaderValues); + Assert.Empty(result.ModelStateErrors); + } + // The action that this test hits will echo back the model-bound values [Theory] [InlineData("transactionIds", "1e331f25-0869-4c87-8a94-64e6e40cb5a0")] @@ -187,6 +217,70 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("Title", error); } + // This model sets a value for 'Title', and the model binder won't trounce it. + // + // There's no validation error because we validate the initialized value. + [Fact] + public async Task FromHeader_BindHeader_ToModel_NoValues_InitializedValue_ValidationError() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage( + HttpMethod.Get, + "http://localhost/Blog/BindToModelWithInitializedValue?author=Marvin"); + + // Intentionally not setting a title or tags + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("How to Make Soup", result.HeaderValue); + Assert.Equal(new[] { "Cooking" }, result.HeaderValues); + + var error = Assert.Single(result.ModelStateErrors); + Assert.Equal("Title", error); + } + + // This model uses default value for 'Title'. + // + // There's no validation error because we validate the default value. + [Fact] + public async Task FromHeader_BindHeader_ToModel_NoValues_DefaultValue_NoValidationError() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + var request = new HttpRequestMessage( + HttpMethod.Get, + "http://localhost/Blog/BindToModelWithDefaultValue?author=Marvin"); + + // Intentionally not setting a title or tags + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("How to Make Soup", result.HeaderValue); + Assert.Equal(new[] { "Cooking" }, result.HeaderValues); + + var error = Assert.Single(result.ModelStateErrors); + Assert.Equal("Title", error); + } + private class Result { public string HeaderValue { get; set; } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ComplexModelDtoResultTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ComplexModelDtoResultTest.cs index 4380dc5ca9..0330680e16 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ComplexModelDtoResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/ComplexModelDtoResultTest.cs @@ -14,10 +14,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding.Test var validationNode = GetValidationNode(); // Act - var result = new ComplexModelDtoResult("some string", validationNode); + var result = new ComplexModelDtoResult( + "some string", + isModelBound: true, + validationNode: validationNode); // Assert Assert.Equal("some string", result.Model); + Assert.True(result.IsModelBound); Assert.Equal(validationNode, result.ValidationNode); } diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs index b2a583427d..67c2d6cf97 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/MutableObjectModelBinderTest.cs @@ -583,7 +583,9 @@ namespace Microsoft.AspNet.Mvc.ModelBinding "ValueTypeRequired", "FirstName", "LastName", - "PropertyWithDefaultValue" + "PropertyWithDefaultValue", + "PropertyWithInitializedValue", + "PropertyWithInitializedValueAndDefault", }; var bindingContext = new ModelBindingContext { @@ -751,7 +753,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties); var nameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "Name"); - dto.Results[nameProperty] = new ComplexModelDtoResult("John Doe", new ModelValidationNode(nameProperty, "")); + dto.Results[nameProperty] = new ComplexModelDtoResult( + "John Doe", + isModelBound: true, + validationNode: new ModelValidationNode(nameProperty, "")); var testableBinder = new TestableMutableObjectModelBinder(); @@ -805,14 +810,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var testableBinder = new TestableMutableObjectModelBinder(); var propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Name"); - dto.Results[propertyMetadata] = - new ComplexModelDtoResult("John Doe", new ModelValidationNode(propertyMetadata, "theModel.Name")); + dto.Results[propertyMetadata] = new ComplexModelDtoResult( + "John Doe", + isModelBound: true, + validationNode: new ModelValidationNode(propertyMetadata, "theModel.Name")); // Attempt to set non-Nullable property to null. BindRequiredAttribute should not be relevant in this // case because the binding exists. propertyMetadata = dto.PropertyMetadata.Single(o => o.PropertyName == "Age"); - dto.Results[propertyMetadata] = - new ComplexModelDtoResult(null, new ModelValidationNode(propertyMetadata, "theModel.Age")); + dto.Results[propertyMetadata] = new ComplexModelDtoResult( + null, + isModelBound: true, + validationNode: new ModelValidationNode(propertyMetadata, "theModel.Age")); // Act; must also Validate because null-check error handler is late-bound testableBinder.ProcessDto(bindingContext, dto); @@ -894,11 +903,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Make Age valid and City invalid. var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "Age"); - dto.Results[propertyMetadata] = - new ComplexModelDtoResult(23, new ModelValidationNode(propertyMetadata, "theModel.Age")); + dto.Results[propertyMetadata] = new ComplexModelDtoResult( + 23, + isModelBound: true, + validationNode: new ModelValidationNode(propertyMetadata, "theModel.Age")); + propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "City"); - dto.Results[propertyMetadata] = - new ComplexModelDtoResult(null, new ModelValidationNode(propertyMetadata, "theModel.City")); + dto.Results[propertyMetadata] = new ComplexModelDtoResult( + null, + isModelBound: true, + validationNode: new ModelValidationNode(propertyMetadata, "theModel.City")); // Act testableBinder.ProcessDto(bindingContext, dto); @@ -963,8 +977,10 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Make ValueTypeRequired invalid. var propertyMetadata = dto.PropertyMetadata.Single(p => p.PropertyName == "ValueTypeRequired"); - dto.Results[propertyMetadata] = - new ComplexModelDtoResult(null, new ModelValidationNode(propertyMetadata, "theModel.ValueTypeRequired")); + dto.Results[propertyMetadata] = new ComplexModelDtoResult( + null, + isModelBound: true, + validationNode: new ModelValidationNode(propertyMetadata, "theModel.ValueTypeRequired")); // Act testableBinder.ProcessDto(bindingContext, dto); @@ -999,9 +1015,17 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var dto = new ComplexModelDto(containerMetadata, containerMetadata.Properties); var firstNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "FirstName"); - dto.Results[firstNameProperty] = new ComplexModelDtoResult("John", new ModelValidationNode(firstNameProperty, "")); + dto.Results[firstNameProperty] = new ComplexModelDtoResult( + "John", + isModelBound: true, + validationNode: new ModelValidationNode(firstNameProperty, "")); + var lastNameProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "LastName"); - dto.Results[lastNameProperty] = new ComplexModelDtoResult("Doe", new ModelValidationNode(lastNameProperty, "")); + dto.Results[lastNameProperty] = new ComplexModelDtoResult( + "Doe", + isModelBound: true, + validationNode: new ModelValidationNode(lastNameProperty, "")); + var dobProperty = dto.PropertyMetadata.Single(o => o.PropertyName == "DateOfBirth"); dto.Results[dobProperty] = null; @@ -1025,7 +1049,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var propertyMetadata = bindingContext.ModelMetadata.Properties.First(o => o.PropertyName == "PropertyWithDefaultValue"); var validationNode = new ModelValidationNode(propertyMetadata, "foo"); - var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode); + + var dtoResult = new ComplexModelDtoResult( + model: null, + isModelBound: false, + validationNode: validationNode); + var requiredValidator = bindingContext.OperationBindingContext .ValidatorProvider .GetValidators(propertyMetadata) @@ -1034,7 +1063,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var testableBinder = new TestableMutableObjectModelBinder(); // Act - testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator); + testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert var person = Assert.IsType(bindingContext.Model); @@ -1042,6 +1071,60 @@ namespace Microsoft.AspNet.Mvc.ModelBinding Assert.True(bindingContext.ModelState.IsValid); } + [Fact] + public void SetProperty_PropertyIsPreinitialized_NoValue_DoesNothing() + { + // Arrange + var bindingContext = CreateContext(GetMetadataForObject(new Person())); + + var propertyMetadata = bindingContext.ModelMetadata.Properties.Single( + o => o.PropertyName == "PropertyWithInitializedValue"); + var validationNode = new ModelValidationNode(propertyMetadata, "foo"); + + // This value won't be used because IsModelBound = false. + var dtoResult = new ComplexModelDtoResult( + model: "bad-value", + isModelBound: false, + validationNode: validationNode); + + var testableBinder = new TestableMutableObjectModelBinder(); + + // Act + testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null); + + // Assert + var person = Assert.IsType(bindingContext.Model); + Assert.Equal("preinitialized", person.PropertyWithInitializedValue); + Assert.True(bindingContext.ModelState.IsValid); + } + + [Fact] + public void SetProperty_PropertyIsPreinitialized_WithDefaultValue_NoValue_CallsSetter() + { + // Arrange + var bindingContext = CreateContext(GetMetadataForObject(new Person())); + + var propertyMetadata = bindingContext.ModelMetadata.Properties.Single( + o => o.PropertyName == "PropertyWithInitializedValueAndDefault"); + var validationNode = new ModelValidationNode(propertyMetadata, "foo"); + + // This value won't be used because IsModelBound = false. + var dtoResult = new ComplexModelDtoResult( + model: "bad-value", + isModelBound: false, + validationNode: validationNode); + + var testableBinder = new TestableMutableObjectModelBinder(); + + // Act + testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null); + + // Assert + var person = Assert.IsType(bindingContext.Model); + Assert.Equal("default", person.PropertyWithInitializedValueAndDefault); + Assert.True(bindingContext.ModelState.IsValid); + } + [Fact] public void SetProperty_PropertyIsReadOnly_DoesNothing() { @@ -1049,12 +1132,16 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var bindingContext = CreateContext(GetMetadataForObject(new Person())); var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NonUpdateableProperty"); var validationNode = new ModelValidationNode(propertyMetadata, "foo"); - var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode); + + var dtoResult = new ComplexModelDtoResult( + model: null, + isModelBound: false, + validationNode: validationNode); var testableBinder = new TestableMutableObjectModelBinder(); // Act - testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator: null); + testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null); // Assert // If didn't throw, success! @@ -1069,7 +1156,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth"); var validationNode = new ModelValidationNode(propertyMetadata, "foo"); - var dtoResult = new ComplexModelDtoResult(new DateTime(2001, 1, 1), validationNode); + + var dtoResult = new ComplexModelDtoResult( + new DateTime(2001, 1, 1), + isModelBound: true, + validationNode: validationNode); + var requiredValidator = bindingContext.OperationBindingContext .ValidatorProvider .GetValidators(propertyMetadata) @@ -1079,7 +1171,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var testableBinder = new TestableMutableObjectModelBinder(); // Act - testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator); + testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert validationNode.Validate(validationContext); @@ -1100,12 +1192,15 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfDeath"); var validationNode = new ModelValidationNode(propertyMetadata, "foo"); - var dtoResult = new ComplexModelDtoResult(new DateTime(1800, 1, 1), validationNode); + var dtoResult = new ComplexModelDtoResult( + new DateTime(1800, 1, 1), + isModelBound: true, + validationNode: validationNode); var testableBinder = new TestableMutableObjectModelBinder(); // Act - testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator: null); + testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator: null); // Assert Assert.Equal("Date of death can't be before date of birth." + Environment.NewLine @@ -1118,16 +1213,21 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { // Arrange var bindingContext = CreateContext(GetMetadataForObject(new Person())); + var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "DateOfBirth"); var validationNode = new ModelValidationNode(propertyMetadata, "foo"); - var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode); + var dtoResult = new ComplexModelDtoResult( + model: null, + isModelBound: true, + validationNode: validationNode); + var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata); var validationContext = new ModelValidationContext(bindingContext, propertyMetadata); var testableBinder = new TestableMutableObjectModelBinder(); // Act - testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator); + testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert Assert.True(bindingContext.ModelState.IsValid); @@ -1141,15 +1241,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Arrange var bindingContext = CreateContext(GetMetadataForObject(new Person())); bindingContext.ModelName = " foo"; + var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "ValueTypeRequired"); var validationNode = new ModelValidationNode(propertyMetadata, "foo.ValueTypeRequired"); - var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode); + var dtoResult = new ComplexModelDtoResult( + model: null, + isModelBound: true, + validationNode: validationNode); + var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata); var testableBinder = new TestableMutableObjectModelBinder(); // Act - testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator); + testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert Assert.False(bindingContext.ModelState.IsValid); @@ -1163,15 +1268,20 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Arrange var bindingContext = CreateContext(GetMetadataForObject(new ModelWhosePropertySetterThrows())); bindingContext.ModelName = "foo"; + var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "NameNoAttribute"); var validationNode = new ModelValidationNode(propertyMetadata, "foo.NameNoAttribute"); - var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode); + var dtoResult = new ComplexModelDtoResult( + model: null, + isModelBound: true, + validationNode: validationNode); + var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata); var testableBinder = new TestableMutableObjectModelBinder(); // Act - testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator); + testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert Assert.False(bindingContext.ModelState.IsValid); @@ -1187,15 +1297,19 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // Arrange var bindingContext = CreateContext(GetMetadataForObject(new ModelWhosePropertySetterThrows())); bindingContext.ModelName = "foo"; + var propertyMetadata = bindingContext.ModelMetadata.Properties.Single(o => o.PropertyName == "Name"); var validationNode = new ModelValidationNode(propertyMetadata, "foo.Name"); - var dtoResult = new ComplexModelDtoResult(model: null, validationNode: validationNode); + var dtoResult = new ComplexModelDtoResult(model: null, + isModelBound: true, + validationNode: validationNode); + var requiredValidator = GetRequiredValidator(bindingContext, propertyMetadata); var testableBinder = new TestableMutableObjectModelBinder(); // Act - testableBinder.SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator); + testableBinder.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator); // Assert Assert.False(bindingContext.ModelState.IsValid); @@ -1290,6 +1404,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding [DefaultValue(typeof(decimal), "123.456")] public decimal PropertyWithDefaultValue { get; set; } + + public string PropertyWithInitializedValue { get; set; } = "preinitialized"; + + [DefaultValue("default")] + public string PropertyWithInitializedValueAndDefault { get; set; } = "preinitialized"; } private class PersonWithBindExclusion @@ -1497,20 +1616,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding return base.GetMetadataForProperties(bindingContext); } - public virtual void SetPropertyPublic(ModelBindingContext bindingContext, - ModelMetadata propertyMetadata, - ComplexModelDtoResult dtoResult, - IModelValidator requiredValidator) - { - base.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator); - } - - protected override void SetProperty(ModelBindingContext bindingContext, + public new void SetProperty(ModelBindingContext bindingContext, ModelMetadata propertyMetadata, ComplexModelDtoResult dtoResult, IModelValidator requiredValidator) { - SetPropertyPublic(bindingContext, propertyMetadata, dtoResult, requiredValidator); + base.SetProperty(bindingContext, propertyMetadata, dtoResult, requiredValidator); } } } diff --git a/test/WebSites/FiltersWebSite/Controllers/ActionFilterController.cs b/test/WebSites/FiltersWebSite/Controllers/ActionFilterController.cs index 59b90b73b9..0b5bcde811 100644 --- a/test/WebSites/FiltersWebSite/Controllers/ActionFilterController.cs +++ b/test/WebSites/FiltersWebSite/Controllers/ActionFilterController.cs @@ -30,12 +30,19 @@ namespace FiltersWebSite public override void OnActionExecuting(ActionExecutingContext context) { - if (context.ActionArguments["fromGlobalActionFilter"] == null) + object obj; + List filters; + + if (context.ActionArguments.TryGetValue("fromGlobalActionFilter", out obj)) { - context.ActionArguments["fromGlobalActionFilter"] = new List(); + filters = (List)obj; } - (context.ActionArguments["fromGlobalActionFilter"] as List) - .Add(Helpers.GetContentResult(context.Result, "Controller override - OnActionExecuting")); + { + filters = new List(); + context.ActionArguments.Add("fromGlobalActionFilter", filters); + } + + filters.Add(Helpers.GetContentResult(context.Result, "Controller override - OnActionExecuting")); } public override void OnActionExecuted(ActionExecutedContext context) diff --git a/test/WebSites/ModelBindingWebSite/Controllers/FromHeader_BlogController.cs b/test/WebSites/ModelBindingWebSite/Controllers/FromHeader_BlogController.cs index eafaf0bc4d..3a5bdae409 100644 --- a/test/WebSites/ModelBindingWebSite/Controllers/FromHeader_BlogController.cs +++ b/test/WebSites/ModelBindingWebSite/Controllers/FromHeader_BlogController.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; using Microsoft.AspNet.Mvc; @@ -21,6 +22,17 @@ namespace ModelBindingWebSite.Controllers }; } + // Echo back the header value + [HttpGet("BindToStringParameterDefaultValue")] + public object BindToStringParameterDefaultValue([FromHeader] string transactionId = "default-value") + { + return new Result() + { + HeaderValue = transactionId, + ModelStateErrors = ModelState.Where(kvp => kvp.Value.Errors.Count > 0).Select(kvp => kvp.Key).ToArray(), + }; + } + // Echo back the header values [HttpGet("BindToStringArrayParameter")] public object BindToStringArrayParameter([FromHeader] string[] transactionIds) @@ -53,6 +65,29 @@ namespace ModelBindingWebSite.Controllers }; } + [HttpGet("BindToModelWithInitializedValue")] + public object BindToModelWithInitializedValue(BlogPostWithInitializedValue blogPost) + { + return new Result() + { + HeaderValue = blogPost.Title, + HeaderValues = blogPost.Tags, + ModelStateErrors = ModelState.Where(kvp => kvp.Value.Errors.Count > 0).Select(kvp => kvp.Key).ToArray(), + }; + } + + [HttpGet("BindToModelWithDefaultValue")] + public object BindToModelWithDefaultValue(BlogPostWithDefaultValue blogPost) + { + return new Result() + { + HeaderValue = blogPost.Title, + HeaderValues = blogPost.Tags, + ModelStateErrors = ModelState.Where(kvp => kvp.Value.Errors.Count > 0).Select(kvp => kvp.Key).ToArray(), + }; + } + + private class Result { public string HeaderValue { get; set; } @@ -73,5 +108,31 @@ namespace ModelBindingWebSite.Controllers public string Author { get; set; } } + + public class BlogPostWithInitializedValue + { + [Required] + [FromHeader] + public string Title { get; set; } = "How to Make Soup"; + + [FromHeader] + public string[] Tags { get; set; } = new string[] { "Cooking" }; + + public string Author { get; set; } + } + + public class BlogPostWithDefaultValue + { + [Required] + [FromHeader] + [DefaultValue("How to Make Soup")] + public string Title { get; set; } + + [FromHeader] + [DefaultValue(new string[] { "Cooking" })] + public string[] Tags { get; set; } + + public string Author { get; set; } + } } } \ No newline at end of file From 12c2759cec61674eb00f60a66a4df73b1af0e309 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 14 Jan 2015 18:35:20 -0800 Subject: [PATCH 097/118] Fix for #384 - And some other changes to controller as filter This is a major change to how we handle the scenario where a controller is a filter. We want to change the lifetime of the controller object, by scoping it around action filters and result filters. This means that a controller class can only implement action filters and result filters. To implement #384 - we're creating a delegating filter class 'ControllerFilter' which will forward calls to the implementation of the controller. This is discovered in the controller model and added to the filter collection. This filter is removable as an opt-out of this feature. The ControllerFilter only implements action filter and result filter, so the new restriction about filter types on Controller is in place. A future change will move the instantiation of the controller to after resource filters. --- .../DefaultControllerModelBuilder.cs | 16 +++ src/Microsoft.AspNet.Mvc.Core/Controller.cs | 11 +- .../Filters/ControllerActionFilter.cs | 52 +++++++++ .../Filters/ControllerResultFilter.cs | 52 +++++++++ .../Filters/DefaultFilterProvider.cs | 28 ----- .../DefaultControllerModelBuilderTest.cs | 105 ++++++++++++++++++ .../Filters/DefaultFilterProviderTest.cs | 68 ------------ .../FiltersTest.cs | 8 +- .../Logging/LoggingStartupTest.cs | 9 +- .../Controllers/ExceptionOrderController.cs | 10 +- .../Controllers/JsonOnlyController.cs | 21 ++-- .../Controllers/ProductsController.cs | 21 +--- 12 files changed, 251 insertions(+), 150 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Core/Filters/ControllerActionFilter.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Filters/ControllerResultFilter.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs index d4cb7cfed2..e06c9e1e69 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ApplicationModels/DefaultControllerModelBuilder.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; using Microsoft.AspNet.Mvc.Description; +using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.Mvc.Routing; using Microsoft.Framework.Logging; @@ -141,6 +142,21 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels controllerModel.ApiExplorer.GroupName = apiGroupName.GroupName; } + // Controllers can implement action filter and result filter interfaces. We add + // a special delegating filter implementation to the pipeline to handle it. + // + // This is needed because filters are instantiated before the controller. + if (typeof(IAsyncActionFilter).GetTypeInfo().IsAssignableFrom(typeInfo) || + typeof(IActionFilter).GetTypeInfo().IsAssignableFrom(typeInfo)) + { + controllerModel.Filters.Add(new ControllerActionFilter()); + } + if (typeof(IAsyncResultFilter).GetTypeInfo().IsAssignableFrom(typeInfo) || + typeof(IResultFilter).GetTypeInfo().IsAssignableFrom(typeInfo)) + { + controllerModel.Filters.Add(new ControllerResultFilter()); + } + return controllerModel; } diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index e4e4b51226..e5ad680fa9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -16,7 +16,7 @@ using Microsoft.Framework.DependencyInjection; namespace Microsoft.AspNet.Mvc { - public class Controller : IActionFilter, IAsyncActionFilter, IOrderedFilter, IDisposable + public class Controller : IActionFilter, IAsyncActionFilter, IDisposable { private DynamicViewData _viewBag; private ViewDataDictionary _viewData; @@ -137,15 +137,6 @@ namespace Microsoft.AspNet.Mvc } } - int IOrderedFilter.Order - { - get - { - // Controller-filter methods run farthest the action by default. - return int.MinValue; - } - } - /// /// Creates a object that renders a view to the response. /// diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ControllerActionFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ControllerActionFilter.cs new file mode 100644 index 0000000000..d62c4d4131 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ControllerActionFilter.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 System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Core; + +namespace Microsoft.AspNet.Mvc.Filters +{ + /// + /// A filter implementation which delegates to the controller for action filter interfaces. + /// + public class ControllerActionFilter : IAsyncActionFilter, IOrderedFilter + { + // Controller-filter methods run farthest from the action by default. + /// + public int Order { get; set; } = int.MinValue; + + /// + public 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(); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ControllerResultFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ControllerResultFilter.cs new file mode 100644 index 0000000000..3e18e0e587 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ControllerResultFilter.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 System.Threading.Tasks; +using Microsoft.AspNet.Mvc.Core; + +namespace Microsoft.AspNet.Mvc.Filters +{ + /// + /// A filter implementation which delegates to the controller for result filter interfaces. + /// + public class ControllerResultFilter : IAsyncResultFilter, IOrderedFilter + { + // Controller-filter methods run farthest from the result by default. + /// + public int Order { get; set; } = int.MinValue; + + /// + public 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(); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs index 24bbe8e273..70934c4911 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/DefaultFilterProvider.cs @@ -32,12 +32,6 @@ namespace Microsoft.AspNet.Mvc.Filters } } - var controllerFilter = context.ActionContext.Controller as IFilter; - if (controllerFilter != null) - { - InsertControllerAsFilter(context, controllerFilter); - } - if (callNext != null) { callNext(); @@ -73,28 +67,6 @@ namespace Microsoft.AspNet.Mvc.Filters } } - private void InsertControllerAsFilter(FilterProviderContext context, IFilter controllerFilter) - { - var descriptor = new FilterDescriptor(controllerFilter, FilterScope.Controller); - var item = new FilterItem(descriptor, controllerFilter); - - // BinarySearch will return the index of where the item _should_be_ in the list. - // - // If index > 0: - // Other items in the list have the same order and scope - the item was 'found'. - // - // If index < 0: - // No other items in the list have the same order and scope - the item was not 'found' - // Index will be the bitwise compliment of of the 'right' location. - var index = context.Results.BinarySearch(item, FilterItemOrderComparer.Comparer); - if (index < 0) - { - index = ~index; - } - - context.Results.Insert(index, item); - } - private void ApplyFilterToContainer(object actualFilter, IFilter filterMetadata) { Debug.Assert(actualFilter != null, "actualFilter should not be null"); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs index 87481ef450..cb37c4aea9 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ApplicationModel/DefaultControllerModelBuilderTest.cs @@ -2,8 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Reflection; +using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.ApplicationModels.DefaultControllerModelBuilderTestControllers; using Xunit; +using System; +using System.Threading.Tasks; namespace Microsoft.AspNet.Mvc.ApplicationModels { @@ -163,6 +166,67 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels Assert.True(isController); } + [Fact] + public void BuildControllerModel_DerivedFromControllerClass_HasFilter() + { + // Arrange + var builder = new AccessibleControllerModelBuilder(); + var typeInfo = typeof(StoreController).GetTypeInfo(); + + // Act + var model = builder.BuildControllerModel(typeInfo); + + // Assert + var filter = Assert.Single(model.Filters); + Assert.IsType(filter); + } + + // This class has a filter attribute, but doesn't implement any filter interfaces, + // so ControllerFilter is not present. + [Fact] + public void BuildControllerModel_ClassWithoutFilterInterfaces_HasNoControllerFilter() + { + // Arrange + var builder = new AccessibleControllerModelBuilder(); + var typeInfo = typeof(NoFiltersController).GetTypeInfo(); + + // Act + var model = builder.BuildControllerModel(typeInfo); + + // Assert + var filter = Assert.Single(model.Filters); + Assert.IsType(filter); + } + + [Fact] + public void BuildControllerModel_ClassWithFilterInterfaces_HasFilter() + { + // Arrange + var builder = new AccessibleControllerModelBuilder(); + var typeInfo = typeof(SomeFiltersController).GetTypeInfo(); + + // Act + var model = builder.BuildControllerModel(typeInfo); + + // Assert + Assert.Single(model.Filters, f => f is ControllerActionFilter); + Assert.Single(model.Filters, f => f is ControllerResultFilter); + } + + [Fact] + public void BuildControllerModel_ClassWithFilterInterfaces_UnsupportedType() + { + // Arrange + var builder = new AccessibleControllerModelBuilder(); + var typeInfo = typeof(UnsupportedFiltersController).GetTypeInfo(); + + // Act + var model = builder.BuildControllerModel(typeInfo); + + // Assert + Assert.Empty(model.Filters); + } + private class AccessibleControllerModelBuilder : DefaultControllerModelBuilder { public AccessibleControllerModelBuilder() @@ -220,4 +284,45 @@ namespace Microsoft.AspNet.Mvc.ApplicationModels.DefaultControllerModelBuilderTe public class PocoController { } + + [Produces("application/json")] + public class NoFiltersController + { + } + + public class SomeFiltersController : IAsyncActionFilter, IResultFilter + { + public Task OnActionExecutionAsync( + [NotNull] ActionExecutingContext context, + [NotNull] ActionExecutionDelegate next) + { + return null; + } + + public void OnResultExecuted([NotNull] ResultExecutedContext context) + { + } + + public void OnResultExecuting([NotNull ]ResultExecutingContext context) + { + } + } + + public class UnsupportedFiltersController : IExceptionFilter, IAuthorizationFilter, IAsyncResourceFilter + { + public void OnAuthorization([NotNull]AuthorizationContext context) + { + throw new NotImplementedException(); + } + + public void OnException([NotNull]ExceptionContext context) + { + throw new NotImplementedException(); + } + + public Task OnResourceExecutionAsync([NotNull]ResourceExecutingContext context, [NotNull]ResourceExecutionDelegate next) + { + throw new NotImplementedException(); + } + } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/DefaultFilterProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/DefaultFilterProviderTest.cs index 8b37076e78..b30b13543a 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/DefaultFilterProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/DefaultFilterProviderTest.cs @@ -129,74 +129,6 @@ namespace Microsoft.AspNet.Mvc.Filters Assert.Equal(0, item.Descriptor.Order); } - [Fact] - public void DefaultFilterProvider_InsertsController_DefaultOrder() - { - // Arrange - var filter1 = new Mock(); - filter1.SetupGet(f => f.Order).Returns(int.MinValue); - - var filter2 = new Mock(); - filter2.SetupGet(f => f.Order).Returns(int.MinValue); - - var context = CreateFilterContext(new List() - { - new FilterItem(new FilterDescriptor(filter1.Object, FilterScope.Global)), - new FilterItem(new FilterDescriptor(filter2.Object, FilterScope.Action)), - }); - - var controller = new Controller(); - context.ActionContext.Controller = controller; - - var provider = CreateProvider(); - - // Act - provider.Invoke(context, () => { }); - var results = context.Results; - - // Assert - var controllerItem = results[1]; - Assert.Same(controller, controllerItem.Filter); - Assert.Same(controller, controllerItem.Descriptor.Filter); - Assert.Equal(FilterScope.Controller, controllerItem.Descriptor.Scope); - Assert.Equal(int.MinValue, controllerItem.Descriptor.Order); - } - - [Fact] - public void DefaultFilterProvider_InsertsController_CustomOrder() - { - // Arrange - var filter1 = new Mock(); - filter1.SetupGet(f => f.Order).Returns(0); - - var filter2 = new Mock(); - filter2.SetupGet(f => f.Order).Returns(int.MaxValue); - - var context = CreateFilterContext(new List() - { - new FilterItem(new FilterDescriptor(filter1.Object, FilterScope.Global)), - new FilterItem(new FilterDescriptor(filter2.Object, FilterScope.Action)), - }); - - var controller = new Mock(); - controller.SetupGet(f => f.Order).Returns(17); - - context.ActionContext.Controller = controller.Object; - - var provider = CreateProvider(); - - // Act - provider.Invoke(context, () => { }); - var results = context.Results; - - // Assert - var controllerItem = results[1]; - Assert.Same(controller.Object, controllerItem.Filter); - Assert.Same(controller.Object, controllerItem.Descriptor.Filter); - Assert.Equal(FilterScope.Controller, controllerItem.Descriptor.Scope); - Assert.Equal(17, controllerItem.Descriptor.Order); - } - private DefaultFilterProvider CreateProvider() { var services = new ServiceContainer(); diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs index 27ef0e4dcd..6923c888e8 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/FiltersTest.cs @@ -18,6 +18,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests private readonly IServiceProvider _services = TestHelper.CreateServices("FiltersWebSite"); private readonly Action _app = new FiltersWebSite.Startup().Configure; + // A controller can only be an action filter and result filter, so we don't have entries + // for the other filter types implemented by the controller. [Fact] public async Task ListAllFilters() { @@ -27,11 +29,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var expected = new string[] { - "Controller Override - OnAuthorization", "Global Authorization Filter - OnAuthorization", "On Controller Authorization Filter - OnAuthorization", "Authorize Filter On Action - OnAuthorization", - "Controller Override Resource Filter - OnResourceExecuting", "Global Resource Filter - OnResourceExecuting", "Controller Resource Filter - OnResourceExecuting", "Action Resource Filter - OnResourceExecuting", @@ -55,7 +55,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests "Action Resource Filter - OnResourceExecuted", "Controller Resource Filter - OnResourceExecuted", "Global Resource Filter - OnResourceExecuted", - "Controller Override Resource Filter - OnResourceExecuted", }; // Act @@ -336,7 +335,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("GlobalExceptionFilter.OnException", await response.Content.ReadAsStringAsync()); } - // Controller Override, Action, Controller, and a Global Exception filters are present. + // Action, Controller, and a Global Exception filters are present. // Verifies they are executed in the above mentioned order. [Fact] public async Task ExceptionFilter_Scope() @@ -351,7 +350,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal( - "OnException implemented in Controller, " + "GlobalExceptionFilter.OnException, " + "ControllerExceptionFilter.OnException, " + "Action Exception Filter", diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingStartupTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingStartupTest.cs index dbb285ee25..f4bfe81024 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingStartupTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/Logging/LoggingStartupTest.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using LoggingWebSite; using LoggingWebSite.Controllers; using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Mvc.Filters; using Microsoft.AspNet.Mvc.Logging; using Microsoft.AspNet.TestHost; using Newtonsoft.Json; @@ -79,10 +80,12 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Empty(controller.ApiExplorer.IsVisible); Assert.Empty(controller.ApiExplorer.GroupName.ToString()); Assert.Empty(controller.Attributes); - Assert.Empty(controller.Filters); Assert.Empty(controller.ActionConstraints); Assert.Empty(controller.RouteConstraints); Assert.Empty(controller.AttributeRoutes); + + var filter = Assert.Single(controller.Filters); + Assert.Equal(typeof(ControllerActionFilter).AssemblyQualifiedName, (string)filter.FilterMetadataType); } [Fact] @@ -96,7 +99,6 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests dynamic action = logs.First().State; Assert.Equal("Index", action.Name.ToString()); Assert.Empty(action.Parameters); - Assert.Empty(action.FilterDescriptors); Assert.Equal("action", action.RouteConstraints[0].RouteKey.ToString()); Assert.Equal("controller", action.RouteConstraints[1].RouteKey.ToString()); Assert.Empty(action.RouteValueDefaults); @@ -104,6 +106,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Empty(action.HttpMethods.ToString()); Assert.Empty(action.Properties); Assert.Equal("Home", action.ControllerName.ToString()); + + var filter = Assert.Single(action.FilterDescriptors).Filter; + Assert.Equal(typeof(ControllerActionFilter).AssemblyQualifiedName, (string)filter.FilterMetadataType); } private async Task> GetLogsByDataTypeAsync() diff --git a/test/WebSites/FiltersWebSite/Controllers/ExceptionOrderController.cs b/test/WebSites/FiltersWebSite/Controllers/ExceptionOrderController.cs index ef64272c82..79489f15e0 100644 --- a/test/WebSites/FiltersWebSite/Controllers/ExceptionOrderController.cs +++ b/test/WebSites/FiltersWebSite/Controllers/ExceptionOrderController.cs @@ -7,20 +7,12 @@ using Microsoft.AspNet.Mvc; namespace FiltersWebSite { [ControllerExceptionFilter] - public class ExceptionOrderController : Controller, IExceptionFilter + public class ExceptionOrderController : Controller { [HandleInvalidOperationExceptionFilter] public string GetError(string error) { throw new InvalidOperationException(error); } - - public void OnException(ExceptionContext context) - { - if (context.Exception.GetType() == typeof(InvalidOperationException)) - { - context.Result = Helpers.GetContentResult(context.Result, "OnException implemented in Controller"); - } - } } } \ No newline at end of file diff --git a/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs b/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs index 5b549c1cff..a5800b93d1 100644 --- a/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs +++ b/test/WebSites/FiltersWebSite/Controllers/JsonOnlyController.cs @@ -1,13 +1,15 @@ // 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.Linq; using Microsoft.AspNet.Mvc; namespace FiltersWebSite.Controllers { + [JsonOnly] [Route("Json")] - public class JsonOnlyController : Controller, IResourceFilter + public class JsonOnlyController : Controller { [HttpPost] public string Post([FromBody] DummyClass dummy) @@ -15,16 +17,19 @@ namespace FiltersWebSite.Controllers return (dummy?.SampleInt ?? 0).ToString(); } - public void OnResourceExecuted(ResourceExecutedContext context) + private class JsonOnlyAttribute : Attribute, IResourceFilter { - } + public void OnResourceExecuted(ResourceExecutedContext context) + { + } - public void OnResourceExecuting(ResourceExecutingContext context) - { - var jsonFormatter = context.InputFormatters.OfType().Single(); + public void OnResourceExecuting(ResourceExecutingContext context) + { + var jsonFormatter = context.InputFormatters.OfType().Single(); - context.InputFormatters.Clear(); - context.InputFormatters.Add(jsonFormatter); + context.InputFormatters.Clear(); + context.InputFormatters.Add(jsonFormatter); + } } } } \ No newline at end of file diff --git a/test/WebSites/FiltersWebSite/Controllers/ProductsController.cs b/test/WebSites/FiltersWebSite/Controllers/ProductsController.cs index a039fefa6c..7fa13d9cab 100644 --- a/test/WebSites/FiltersWebSite/Controllers/ProductsController.cs +++ b/test/WebSites/FiltersWebSite/Controllers/ProductsController.cs @@ -11,7 +11,7 @@ namespace FiltersWebSite [ControllerActionFilter] [ControllerAuthorizationFilter] [TracingResourceFilter("Controller Resource Filter")] - public class ProductsController : Controller, IResultFilter, IAuthorizationFilter, IResourceFilter + public class ProductsController : Controller, IResultFilter { [PassThroughResultFilter] [PassThroughActionFilter] @@ -44,24 +44,5 @@ namespace FiltersWebSite { context.HttpContext.Response.Headers.Append("filters", "Controller Override - OnResultExecuting"); } - - public void OnAuthorization(AuthorizationContext context) - { - context.HttpContext.Response.Headers.Append("filters", "Controller Override - OnAuthorization"); - } - - public void OnResourceExecuting(ResourceExecutingContext context) - { - context.HttpContext.Response.Headers.Append( - "filters", - "Controller Override Resource Filter - OnResourceExecuting"); - } - - public void OnResourceExecuted(ResourceExecutedContext context) - { - context.HttpContext.Response.Headers.Append( - "filters", - "Controller Override Resource Filter - OnResourceExecuted"); - } } } \ No newline at end of file From e1069dbf65182e5f13ba6ee863711b61521a2b81 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 14 Jan 2015 18:35:20 -0800 Subject: [PATCH 098/118] Fix for #384 - And some other changes to controller as filter This is a major change to how we handle the scenario where a controller is a filter. We want to change the lifetime of the controller object, by scoping it around action filters and result filters. This means that a controller class can only implement action filters and result filters. To implement #384 - we're creating a delegating filter class 'ControllerFilter' which will forward calls to the implementation of the controller. This is discovered in the controller model and added to the filter collection. This filter is removable as an opt-out of this feature. The ControllerFilter only implements action filter and result filter, so the new restriction about filter types on Controller is in place. A future change will move the instantiation of the controller to after resource filters. --- src/Microsoft.AspNet.Mvc.Core/Controller.cs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/Controller.cs b/src/Microsoft.AspNet.Mvc.Core/Controller.cs index e5ad680fa9..3220131163 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Controller.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Controller.cs @@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Mvc { return ActionContext?.RouteData; } - } + } public ModelStateDictionary ModelState { @@ -113,7 +113,7 @@ namespace Microsoft.AspNet.Mvc return _viewData; } set - { + { if (value == null) { throw @@ -822,7 +822,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using values from the controller's current + /// Updates the specified instance using values from the controller's current /// and a . /// /// The type of the model object. @@ -847,7 +847,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using the and a + /// Updates the specified instance using the and a /// . /// /// The type of the model object. @@ -882,14 +882,14 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using values from the controller's current + /// Updates the specified instance using values from the controller's current /// and a . /// /// The type of the model object. /// The model instance to update. /// The prefix to use when looking up values in the current . /// - /// (s) which represent top-level properties + /// (s) which represent top-level properties /// which need to be included for the current model. /// A that on completion returns true if the update is successful [NonAction] @@ -920,7 +920,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using values from the controller's current + /// Updates the specified instance using values from the controller's current /// and a . /// /// The type of the model object. @@ -957,7 +957,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using the and a + /// Updates the specified instance using the and a /// . /// /// The type of the model object. @@ -965,7 +965,7 @@ namespace Microsoft.AspNet.Mvc /// The prefix to use when looking up values in the /// /// The used for looking up values. - /// (s) which represent top-level properties + /// (s) which represent top-level properties /// which need to be included for the current model. /// A that on completion returns true if the update is successful [NonAction] @@ -997,7 +997,7 @@ namespace Microsoft.AspNet.Mvc } /// - /// Updates the specified instance using the and a + /// Updates the specified instance using the and a /// . /// /// The type of the model object. From 0c5a7022454dccac267b62325372d313f0e69266 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 15 Jan 2015 15:37:01 -0800 Subject: [PATCH 099/118] Changing when controllers are created This change moves controller creation to the stage immediately before model binding. The controller will be disposed/released before Resource Filters run their 'OnResourceExecuted' method. Previously the controller's lifetime surrounded all filter invocation. Additionally, the Controller property is now gone from ActionContext, and is moved to the 4 filter contexts that should have access to it Action*Context and Result*Context. --- .../ActionContext.cs | 6 -- .../ControllerActionInvoker.cs | 19 ++--- .../DefaultControllerFactory.cs | 1 - .../FilterActionInvoker.cs | 84 +++++++++++++++---- .../Filters/ActionExecutedContext.cs | 6 +- .../Filters/ActionExecutingContext.cs | 8 +- .../Filters/ResultExecutedContext.cs | 6 +- .../Filters/ResultExecutingContext.cs | 6 +- .../ControllerActionInvokerTest.cs | 63 +++++++++++--- .../DefaultControllerActivatorTest.cs | 25 ++---- .../Filters/ActionFilterAttributeTests.cs | 10 ++- .../Filters/ProducesAttributeTests.cs | 5 +- .../ControllerActionArgumentBinderTests.cs | 21 +---- .../ResponseCacheAttributeTest.cs | 3 +- .../HttpResponseExceptionActionFilterTest.cs | 31 ++++--- 15 files changed, 184 insertions(+), 110 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs b/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs index ff754b235c..1cd298c758 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionContext.cs @@ -14,7 +14,6 @@ namespace Microsoft.AspNet.Mvc : this(actionContext.HttpContext, actionContext.RouteData, actionContext.ActionDescriptor) { ModelState = actionContext.ModelState; - Controller = actionContext.Controller; } public ActionContext([NotNull] RouteContext routeContext, [NotNull] ActionDescriptor actionDescriptor) @@ -39,10 +38,5 @@ namespace Microsoft.AspNet.Mvc public ModelStateDictionary ModelState { get; private set; } public ActionDescriptor ActionDescriptor { get; private set; } - - /// - /// The controller is available only after the controller factory runs. - /// - public object Controller { get; set; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs index c55ad38441..9c4bdcf9a3 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionInvoker.cs @@ -50,21 +50,16 @@ namespace Microsoft.AspNet.Mvc } } - public async override Task InvokeAsync() + protected override object CreateInstance() { // The binding context is used in activation Debug.Assert(ActionBindingContext != null); - var controller = _controllerFactory.CreateController(ActionContext); + return _controllerFactory.CreateController(ActionContext); + } - try - { - ActionContext.Controller = controller; - await base.InvokeAsync(); - } - finally - { - _controllerFactory.ReleaseController(ActionContext.Controller); - } + protected override void ReleaseInstance(object instance) + { + _controllerFactory.ReleaseController(instance); } protected override async Task InvokeActionAsync(ActionExecutingContext actionExecutingContext) @@ -72,7 +67,7 @@ namespace Microsoft.AspNet.Mvc var actionMethodInfo = _descriptor.MethodInfo; var actionReturnValue = await ControllerActionExecutor.ExecuteAsync( actionMethodInfo, - ActionContext.Controller, + actionExecutingContext.Controller, actionExecutingContext.ActionArguments); var actionResult = CreateActionResult( diff --git a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs index e634dcbfe6..8f88a1594e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs +++ b/src/Microsoft.AspNet.Mvc.Core/DefaultControllerFactory.cs @@ -37,7 +37,6 @@ namespace Microsoft.AspNet.Mvc _serviceProvider, actionDescriptor.ControllerTypeInfo.AsType()); - actionContext.Controller = controller; _controllerActivator.Activate(controller, actionContext); return controller; diff --git a/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs b/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs index 5f433107cf..d077bcb4e4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/FilterActionInvoker.cs @@ -55,8 +55,6 @@ namespace Microsoft.AspNet.Mvc _modelValidatorProviderProvider = modelValidatorProviderProvider; _valueProviderFactoryProvider = valueProviderFactoryProvider; _actionBindingContextAccessor = actionBindingContextAccessor; - - ActionBindingContext = new ActionBindingContext(); } protected ActionContext ActionContext { get; private set; } @@ -73,6 +71,21 @@ namespace Microsoft.AspNet.Mvc } } + protected object Instance { get; private set; } + + /// + /// Called to create an instance of an object which will act as the reciever of the action invocation. + /// + /// The constructed instance or null. + protected abstract object CreateInstance(); + + /// + /// Called to create an instance of an object which will act as the reciever of the action invocation. + /// + /// The instance to release. + /// This method will not be called if returns null. + protected abstract void ReleaseInstance(object instance); + protected abstract Task InvokeActionAsync(ActionExecutingContext actionExecutingContext); protected abstract Task> GetActionArgumentsAsync( @@ -95,7 +108,20 @@ namespace Microsoft.AspNet.Mvc return; } - await InvokeAllResourceFiltersAsync(); + try + { + await InvokeAllResourceFiltersAsync(); + } + finally + { + // Release the instance after all filters have run. We don't need to surround + // Authorizations filters because the instance will be created much later than + // that. + if (Instance != null) + { + 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. @@ -207,7 +233,7 @@ namespace Microsoft.AspNet.Mvc if (item.FilterAsync != null) { await item.FilterAsync.OnResourceExecutionAsync( - _resourceExecutingContext, + _resourceExecutingContext, InvokeResourceFilterAsync); if (_resourceExecutedContext == null) @@ -384,23 +410,30 @@ namespace Microsoft.AspNet.Mvc Debug.Assert(_resourceExecutingContext != null); - Debug.Assert(ActionBindingContext != 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.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); + _actionExecutingContext = new ActionExecutingContext( + ActionContext, + _filters, + arguments, + Instance); + await InvokeActionFilterAsync(); } @@ -429,7 +462,10 @@ namespace Microsoft.AspNet.Mvc if (_actionExecutedContext == null) { // If we get here then the filter didn't call 'next' indicating a short circuit - _actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters) + _actionExecutedContext = new ActionExecutedContext( + _actionExecutingContext, + _filters, + Instance) { Canceled = true, Result = _actionExecutingContext.Result, @@ -443,7 +479,10 @@ namespace Microsoft.AspNet.Mvc if (_actionExecutingContext.Result != null) { // Short-circuited by setting a result. - _actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters) + _actionExecutedContext = new ActionExecutedContext( + _actionExecutingContext, + _filters, + Instance) { Canceled = true, Result = _actionExecutingContext.Result, @@ -457,7 +496,10 @@ namespace Microsoft.AspNet.Mvc else { // All action filters have run, execute the action method. - _actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters) + _actionExecutedContext = new ActionExecutedContext( + _actionExecutingContext, + _filters, + Instance) { Result = await InvokeActionAsync(_actionExecutingContext), }; @@ -466,7 +508,10 @@ namespace Microsoft.AspNet.Mvc catch (Exception exception) { // Exceptions thrown by the action method OR filters bubble back up through ActionExcecutedContext. - _actionExecutedContext = new ActionExecutedContext(_actionExecutingContext, _filters) + _actionExecutedContext = new ActionExecutedContext( + _actionExecutingContext, + _filters, + Instance) { ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) }; @@ -478,7 +523,7 @@ namespace Microsoft.AspNet.Mvc { _cursor.SetStage(FilterStage.ResultFilters); - _resultExecutingContext = new ResultExecutingContext(ActionContext, _filters, result); + _resultExecutingContext = new ResultExecutingContext(ActionContext, _filters, result, Instance); await InvokeResultFilterAsync(); Debug.Assert(_resultExecutingContext != null); @@ -525,7 +570,8 @@ namespace Microsoft.AspNet.Mvc _resultExecutedContext = new ResultExecutedContext( _resultExecutingContext, _filters, - _resultExecutingContext.Result) + _resultExecutingContext.Result, + Instance) { Canceled = true, }; @@ -536,7 +582,8 @@ namespace Microsoft.AspNet.Mvc _resultExecutedContext = new ResultExecutedContext( _resultExecutingContext, _filters, - _resultExecutingContext.Result) + _resultExecutingContext.Result, + Instance) { Canceled = true, }; @@ -552,7 +599,8 @@ namespace Microsoft.AspNet.Mvc _resultExecutedContext = new ResultExecutedContext( _resultExecutingContext, _filters, - _resultExecutingContext.Result) + _resultExecutingContext.Result, + Instance) { Canceled = true, }; @@ -570,7 +618,8 @@ namespace Microsoft.AspNet.Mvc _resultExecutedContext = new ResultExecutedContext( _resultExecutingContext, _filters, - _resultExecutingContext.Result); + _resultExecutingContext.Result, + Instance); } } catch (Exception exception) @@ -578,7 +627,8 @@ namespace Microsoft.AspNet.Mvc _resultExecutedContext = new ResultExecutedContext( _resultExecutingContext, _filters, - _resultExecutingContext.Result) + _resultExecutingContext.Result, + Instance) { ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) }; diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionExecutedContext.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionExecutedContext.cs index 1c6d6bb817..984c21ebd8 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionExecutedContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionExecutedContext.cs @@ -14,13 +14,17 @@ namespace Microsoft.AspNet.Mvc public ActionExecutedContext( [NotNull] ActionContext actionContext, - [NotNull] IList filters) + [NotNull] IList filters, + object controller) : base(actionContext, filters) { + Controller = controller; } public virtual bool Canceled { get; set; } + public virtual object Controller { get; } + public virtual Exception Exception { get diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionExecutingContext.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionExecutingContext.cs index 85756a35c9..80bc25ee9e 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ActionExecutingContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ActionExecutingContext.cs @@ -10,14 +10,18 @@ namespace Microsoft.AspNet.Mvc public ActionExecutingContext( [NotNull] ActionContext actionContext, [NotNull] IList filters, - [NotNull] IDictionary actionArguments) + [NotNull] IDictionary actionArguments, + object controller) : base(actionContext, filters) { ActionArguments = actionArguments; + Controller = controller; } public virtual IActionResult Result { get; set; } - public virtual IDictionary ActionArguments { get; private set; } + public virtual IDictionary ActionArguments { get; } + + public virtual object Controller { get; } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ResultExecutedContext.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ResultExecutedContext.cs index 08e454ebfb..594b61f2e4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ResultExecutedContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ResultExecutedContext.cs @@ -15,14 +15,18 @@ namespace Microsoft.AspNet.Mvc public ResultExecutedContext( [NotNull] ActionContext actionContext, [NotNull] IList filters, - [NotNull] IActionResult result) + [NotNull] IActionResult result, + object controller) : base(actionContext, filters) { Result = result; + Controller = controller; } public virtual bool Canceled { get; set; } + public virtual object Controller { get; } + public virtual Exception Exception { get diff --git a/src/Microsoft.AspNet.Mvc.Core/Filters/ResultExecutingContext.cs b/src/Microsoft.AspNet.Mvc.Core/Filters/ResultExecutingContext.cs index 26d7c7c7ed..3a89d3638f 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Filters/ResultExecutingContext.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Filters/ResultExecutingContext.cs @@ -10,12 +10,16 @@ namespace Microsoft.AspNet.Mvc public ResultExecutingContext( [NotNull] ActionContext actionContext, [NotNull] IList filters, - [NotNull] IActionResult result) + [NotNull] IActionResult result, + object controller) : base(actionContext, filters) { Result = result; + Controller = controller; } + public virtual object Controller { get; } + public virtual IActionResult Result { get; set; } public virtual bool Cancel { get; set; } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs index 3277056e17..c10fb36c67 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionInvokerTest.cs @@ -296,6 +296,7 @@ namespace Microsoft.AspNet.Mvc // Assert filter1.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); + Assert.False(invoker.ControllerFactory.CreateCalled); } [Fact] @@ -325,6 +326,8 @@ namespace Microsoft.AspNet.Mvc filter1.Verify( f => f.OnAuthorizationAsync(It.IsAny()), Times.Once()); + + Assert.False(invoker.ControllerFactory.CreateCalled); } [Fact] @@ -1735,6 +1738,7 @@ namespace Microsoft.AspNet.Mvc // Assert Assert.Same(expected.Object, context.Result); Assert.True(context.Canceled); + Assert.False(invoker.ControllerFactory.CreateCalled); } [Fact] @@ -1786,6 +1790,7 @@ namespace Microsoft.AspNet.Mvc // Assert Assert.Same(expected.Object, context.Result); Assert.True(context.Canceled); + Assert.False(invoker.ControllerFactory.CreateCalled); } [Fact] @@ -1847,6 +1852,8 @@ namespace Microsoft.AspNet.Mvc resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Never()); + + Assert.False(invoker.ControllerFactory.CreateCalled); } [Fact] @@ -1969,10 +1976,6 @@ namespace Microsoft.AspNet.Mvc routeData: new RouteData(), actionDescriptor: actionDescriptor); - var controllerFactory = new Mock(); - controllerFactory.Setup(c => c.CreateController(It.IsAny())).Returns(this); - controllerFactory.Setup(m => m.ReleaseController(this)).Verifiable(); - var filterProvider = new Mock>(MockBehavior.Strict); filterProvider .Setup(fp => fp.Invoke(It.IsAny())) @@ -1985,7 +1988,7 @@ namespace Microsoft.AspNet.Mvc var invoker = new TestControllerActionInvoker( actionContext, filterProvider.Object, - controllerFactory, + new MockControllerFactory(this), actionDescriptor, inputFormattersProvider.Object, Mock.Of(), @@ -1997,8 +2000,6 @@ namespace Microsoft.AspNet.Mvc return invoker; } - - [Fact] public async Task Invoke_UsesDefaultValuesIfNotBound() { @@ -2097,14 +2098,47 @@ namespace Microsoft.AspNet.Mvc } } - public class TestControllerActionInvoker : ControllerActionInvoker + private class MockControllerFactory : IControllerFactory { - private Mock _factoryMock; + private object _controller; + public MockControllerFactory(object controller) + { + _controller = controller; + } + + public bool CreateCalled { get; private set; } + + public bool ReleaseCalled { get; private set; } + + public object CreateController(ActionContext actionContext) + { + CreateCalled = true; + return _controller; + } + + public void ReleaseController(object controller) + { + Assert.NotNull(controller); + Assert.Same(_controller, controller); + ReleaseCalled = true; + } + + public void Verify() + { + if (CreateCalled && !ReleaseCalled) + { + Assert.False(true, "ReleaseController should have been called."); + } + } + } + + private class TestControllerActionInvoker : ControllerActionInvoker + { public TestControllerActionInvoker( ActionContext actionContext, INestedProviderManager filterProvider, - Mock controllerFactoryMock, + MockControllerFactory controllerFactory, ControllerActionDescriptor descriptor, IInputFormattersProvider inputFormattersProvider, IControllerActionArgumentBinder controllerActionArgumentBinder, @@ -2115,7 +2149,7 @@ namespace Microsoft.AspNet.Mvc : base( actionContext, filterProvider, - controllerFactoryMock.Object, + controllerFactory, descriptor, inputFormattersProvider, controllerActionArgumentBinder, @@ -2124,14 +2158,17 @@ namespace Microsoft.AspNet.Mvc valueProviderFactoryProvider, actionBindingContext) { - _factoryMock = controllerFactoryMock; + ControllerFactory = controllerFactory; } + public MockControllerFactory ControllerFactory { get; } + public async override Task InvokeAsync() { await base.InvokeAsync(); - _factoryMock.Verify(); + // Make sure that the controller was disposed in every test that creates ones. + ControllerFactory.Verify(); } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerActivatorTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerActivatorTest.cs index d19d772270..094194172e 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerActivatorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/DefaultControllerActivatorTest.cs @@ -29,10 +29,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test .Returns(services); var routeContext = new RouteContext(httpContext.Object); var controller = new TestController(); - var context = new ActionContext(routeContext, new ActionDescriptor()) - { - Controller = controller - }; + var context = new ActionContext(routeContext, new ActionDescriptor()); var activator = new DefaultControllerActivator(); // Act @@ -55,10 +52,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test .Returns(services); var routeContext = new RouteContext(httpContext.Object); var controller = new TestController(); - var context = new ActionContext(routeContext, new ActionDescriptor()) - { - Controller = controller - }; + var context = new ActionContext(routeContext, new ActionDescriptor()); var activator = new DefaultControllerActivator(); // Act @@ -83,10 +77,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test var routeContext = new RouteContext(httpContext.Object); var controller = new TestController(); - var context = new ActionContext(routeContext, new ActionDescriptor()) - { - Controller = controller - }; + var context = new ActionContext(routeContext, new ActionDescriptor()); var activator = new DefaultControllerActivator(); @@ -109,10 +100,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test .Returns(services); var routeContext = new RouteContext(httpContext.Object); var controller = new TestController(); - var context = new ActionContext(routeContext, new ActionDescriptor()) - { - Controller = controller - }; + var context = new ActionContext(routeContext, new ActionDescriptor()); var activator = new DefaultControllerActivator(); // Act @@ -135,10 +123,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test .Returns(services); var routeContext = new RouteContext(httpContext.Object); var controller = new TestController(); - var context = new ActionContext(routeContext, new ActionDescriptor()) - { - Controller = controller - }; + var context = new ActionContext(routeContext, new ActionDescriptor()); var activator = new DefaultControllerActivator(); // Act diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/ActionFilterAttributeTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/ActionFilterAttributeTests.cs index 1bcf9095c0..b450b7d8b4 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/ActionFilterAttributeTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/ActionFilterAttributeTests.cs @@ -227,12 +227,13 @@ namespace Microsoft.AspNet.Mvc.Test return new ActionExecutingContext( CreateActionContext(), new IFilter[] { filter, }, - new Dictionary()); + new Dictionary(), + controller: new object()); } private static ActionExecutedContext CreateActionExecutedContext(ActionExecutingContext context) { - return new ActionExecutedContext(context, context.Filters) + return new ActionExecutedContext(context, context.Filters, context.Controller) { Result = context.Result, }; @@ -243,12 +244,13 @@ namespace Microsoft.AspNet.Mvc.Test return new ResultExecutingContext( CreateActionContext(), new IFilter[] { filter, }, - new NoOpResult()); + new NoOpResult(), + controller: new object()); } private static ResultExecutedContext CreateResultExecutedContext(ResultExecutingContext context) { - return new ResultExecutedContext(context, context.Filters, context.Result); + return new ResultExecutedContext(context, context.Filters, context.Result, context.Controller); } private static ActionContext CreateActionContext() diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/ProducesAttributeTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/ProducesAttributeTests.cs index a939a94e48..3e723bb0d8 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Filters/ProducesAttributeTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Filters/ProducesAttributeTests.cs @@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.Test private static ResultExecutedContext CreateResultExecutedContext(ResultExecutingContext context) { - return new ResultExecutedContext(context, context.Filters, context.Result); + return new ResultExecutedContext(context, context.Filters, context.Result, context.Controller); } private static ResultExecutingContext CreateResultExecutingContext(IFilter filter) @@ -70,7 +70,8 @@ namespace Microsoft.AspNet.Mvc.Test return new ResultExecutingContext( CreateActionContext(), new IFilter[] { filter, }, - new ObjectResult("Some Value")); + new ObjectResult("Some Value"), + controller: new object()); } private static ActionContext CreateActionContext() diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs index 7116349977..739913a204 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ParameterBinding/ControllerActionArgumentBinderTests.cs @@ -190,12 +190,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test }) .Returns(Task.FromResult(result: false)); - var actionContext = new ActionContext( - new RouteContext(Mock.Of()), - actionDescriptor) - { - Controller = Mock.Of(), - }; + var actionContext = new ActionContext(new RouteContext(Mock.Of()), actionDescriptor); var actionBindingContext = new ActionBindingContext() { @@ -243,12 +238,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test }) .Returns(Task.FromResult(result: true)); - var actionContext = new ActionContext( - new RouteContext(Mock.Of()), - actionDescriptor) - { - Controller = Mock.Of(), - }; + var actionContext = new ActionContext(new RouteContext(Mock.Of()), actionDescriptor); var actionBindingContext = new ActionBindingContext() { @@ -303,12 +293,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test }) .Returns(Task.FromResult(result: true)); - var actionContext = new ActionContext( - new RouteContext(Mock.Of()), - actionDescriptor) - { - Controller = Mock.Of(), - }; + var actionContext = new ActionContext(new RouteContext(Mock.Of()), actionDescriptor); var actionBindingContext = new ActionBindingContext() { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ResponseCacheAttributeTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ResponseCacheAttributeTest.cs index 76e9489198..99fea13d8d 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ResponseCacheAttributeTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ResponseCacheAttributeTest.cs @@ -236,7 +236,8 @@ namespace Microsoft.AspNet.Mvc return new ActionExecutingContext( new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()), filters ?? new List(), - new Dictionary()); + new Dictionary(), + new object()); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseExceptionActionFilterTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseExceptionActionFilterTest.cs index 282f4929d4..d8452740c7 100644 --- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseExceptionActionFilterTest.cs +++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/HttpResponseExceptionActionFilterTest.cs @@ -34,12 +34,17 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim { // Arrange var filter = new HttpResponseExceptionActionFilter(); - var context = new ActionExecutingContext(new ActionContext( - new DefaultHttpContext(), - new RouteData(), - actionDescriptor: Mock.Of()), - filters: Mock.Of>(), - actionArguments: new Dictionary()); + + var actionContext = new ActionContext( + new DefaultHttpContext(), + new RouteData(), + Mock.Of()); + + var context = new ActionExecutingContext( + actionContext, + filters: new List(), + actionArguments: new Dictionary(), + controller: new object()); // Act filter.OnActionExecuting(context); @@ -56,12 +61,16 @@ namespace Microsoft.AspNet.Mvc.WebApiCompatShim var httpContext = new DefaultHttpContext(); httpContext.Request.Method = "GET"; + var actionContext = new ActionContext( + httpContext, + new RouteData(), + Mock.Of()); + var context = new ActionExecutedContext( - new ActionContext( - httpContext, - new RouteData(), - actionDescriptor: Mock.Of()), - filters: null); + actionContext, + filters: new List(), + controller: new object()); + context.Exception = new HttpResponseException(HttpStatusCode.BadRequest); // Act From d34554e3fffe15600eeaed9146c4d516ce7a5746 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Sun, 18 Jan 2015 21:06:12 -0800 Subject: [PATCH 100/118] Handle HttpFeature rename --- src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs | 2 +- .../Formatters/HttpResponseMessageOutputFormatter.cs | 2 +- .../ActionResults/FilePathResultTest.cs | 2 +- test/WebSites/FilesWebSite/SendFileMiddleware.cs | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs index 56fbba95ab..4a5e1baabb 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs @@ -8,7 +8,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.FileSystems; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; -using Microsoft.AspNet.HttpFeature; +using Microsoft.AspNet.Http.Interfaces; using Microsoft.AspNet.Mvc.Core; using Microsoft.Framework.DependencyInjection; diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs index 3871042540..154b2100cb 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; using System.Threading.Tasks; -using Microsoft.AspNet.HttpFeature; +using Microsoft.AspNet.Http.Interfaces; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc.WebApiCompatShim diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs index 187d236012..b84bedfe56 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs @@ -7,7 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNet.FileSystems; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http.Core; -using Microsoft.AspNet.HttpFeature; +using Microsoft.AspNet.Http.Interfaces; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; diff --git a/test/WebSites/FilesWebSite/SendFileMiddleware.cs b/test/WebSites/FilesWebSite/SendFileMiddleware.cs index 596eb250ba..13aa99dbcb 100644 --- a/test/WebSites/FilesWebSite/SendFileMiddleware.cs +++ b/test/WebSites/FilesWebSite/SendFileMiddleware.cs @@ -6,7 +6,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; -using Microsoft.AspNet.HttpFeature; +using Microsoft.AspNet.Http.Interfaces; using Microsoft.Framework.Runtime; namespace FilesWebSite From 67b078862ee36a846af1b81088e3f2f73f5de3e2 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Mon, 19 Jan 2015 11:51:16 -0800 Subject: [PATCH 101/118] Fixed content-disposition quoted filename bug --- .../Binders/FormFileModelBinder.cs | 5 ++-- .../Binders/FormFileModelBinderTest.cs | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormFileModelBinder.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormFileModelBinder.cs index 79abd05f59..e8796a45b4 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormFileModelBinder.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Binders/FormFileModelBinder.cs @@ -62,12 +62,13 @@ namespace Microsoft.AspNet.Mvc.ModelBinding // If there is an in the form and is left blank. if (parsedContentDisposition == null || - (file.Length == 0 && string.IsNullOrEmpty(parsedContentDisposition.FileName))) + (file.Length == 0 && + string.IsNullOrEmpty(HeaderUtilities.RemoveQuotes(parsedContentDisposition.FileName)))) { continue; } - var modelName = parsedContentDisposition.Name; + var modelName = HeaderUtilities.RemoveQuotes(parsedContentDisposition.Name); if (modelName.Equals(bindingContext.ModelName, StringComparison.OrdinalIgnoreCase)) { postedFiles.Add(file); diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/FormFileModelBinderTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/FormFileModelBinderTest.cs index c2a63ce342..68b2f3232a 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/FormFileModelBinderTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Binders/FormFileModelBinderTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; +using System.Text; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNet.Http; @@ -36,6 +37,26 @@ namespace Microsoft.AspNet.Mvc.ModelBinding Assert.Equal(2, files.Count); } + [Fact] + public async Task FormFileModelBinder_FilesWithQuotedContentDisposition_BindSuccessful() + { + // Arrange + var formFiles = new FormFileCollection(); + formFiles.Add(GetMockFormFileWithQuotedContentDisposition("file", "file1.txt")); + formFiles.Add(GetMockFormFileWithQuotedContentDisposition("file", "file2.txt")); + var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles)); + var bindingContext = GetBindingContext(typeof(IEnumerable), httpContext); + var binder = new FormFileModelBinder(); + + // Act + var result = await binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(result); + var files = Assert.IsAssignableFrom>(bindingContext.Model); + Assert.Equal(2, files.Count); + } + [Fact] public async Task FormFileModelBinder_ExpectSingleFile_BindFirstFile() { @@ -169,6 +190,14 @@ namespace Microsoft.AspNet.Mvc.ModelBinding .Returns(string.Format("form-data; name={0}; filename={1}", modelName, filename)); return formFile.Object; } + + private static IFormFile GetMockFormFileWithQuotedContentDisposition(string modelName, string filename) + { + var formFile = new Mock(); + formFile.Setup(f => f.ContentDisposition) + .Returns(string.Format("form-data; name=\"{0}\"; filename=\"{1}\"", modelName, filename)); + return formFile.Object; + } } } From 951ed05893d10beafbbe6449652684c19f2384ae Mon Sep 17 00:00:00 2001 From: Kirthi Krishnamraju Date: Sun, 18 Jan 2015 11:59:59 -0800 Subject: [PATCH 102/118] Added SetAntiForgeryCookieAndHeader method that sets cookie token and header --- .../AntiForgery/AntiForgery.cs | 9 ++ .../AntiForgery/AntiForgeryWorker.cs | 71 ++++++++--- src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs | 19 ++- .../AntiXsrf/AntiForgeryWorkerTest.cs | 23 ++++ .../AntiForgeryTests.cs | 112 ++++++++++++++++++ .../Controllers/AccountController.cs | 36 ++++++ .../Views/Account/FlushAsyncLogin.cshtml | 43 +++++++ .../Account/FlushWithoutUpdatingHeader.cshtml | 37 ++++++ .../Views/Shared/_FlushAsyncLayout.cshtml | 15 +++ 9 files changed, 345 insertions(+), 20 deletions(-) create mode 100644 test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml create mode 100644 test/WebSites/AntiForgeryWebSite/Views/Account/FlushWithoutUpdatingHeader.cshtml create mode 100644 test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs index 644ee99287..aedab3ceb5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs @@ -97,5 +97,14 @@ namespace Microsoft.AspNet.Mvc { Validate(context, antiForgeryTokenSet.CookieToken, antiForgeryTokenSet.FormToken); } + + /// + /// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers. + /// + /// The HTTP context associated with the current call. + public void SetCookieTokenAndHeader([NotNull] HttpContext context) + { + _worker.SetCookieTokenAndHeader(context); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs index a2056fe0e3..564fef4064 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs @@ -102,19 +102,8 @@ namespace Microsoft.AspNet.Mvc var tokenSet = GetTokens(httpContext, oldCookieToken); var newCookieToken = tokenSet.CookieToken; var formToken = tokenSet.FormToken; - if (newCookieToken != null) - { - // If a new cookie was generated, persist it. - _tokenStore.SaveCookieToken(httpContext, newCookieToken); - } - if (!_config.SuppressXFrameOptionsHeader) - { - // Adding X-Frame-Options header to prevent ClickJacking. See - // http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10 - // for more information. - httpContext.Response.Headers.Set("X-Frame-Options", "SAMEORIGIN"); - } + SaveCookieTokenAndHeader(httpContext, newCookieToken); // var retVal = new TagBuilder("input"); @@ -143,15 +132,11 @@ namespace Microsoft.AspNet.Mvc private AntiForgeryTokenSetInternal GetTokens(HttpContext httpContext, AntiForgeryToken oldCookieToken) { - AntiForgeryToken newCookieToken = null; - if (!_validator.IsCookieTokenValid(oldCookieToken)) + var newCookieToken = ValidateAndGenerateNewToken(oldCookieToken); + if (newCookieToken != null) { - // Need to make sure we're always operating with a good cookie token. - oldCookieToken = newCookieToken = _generator.GenerateCookieToken(); + oldCookieToken = newCookieToken; } - - Debug.Assert(_validator.IsCookieTokenValid(oldCookieToken)); - var formToken = _generator.GenerateFormToken( httpContext, ExtractIdentity(httpContext), @@ -204,6 +189,54 @@ namespace Microsoft.AspNet.Mvc deserializedFormToken); } + + /// + /// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers. + /// + /// The HTTP context associated with the current call. + public void SetCookieTokenAndHeader([NotNull] HttpContext httpContext) + { + CheckSSLConfig(httpContext); + + var oldCookieToken = GetCookieTokenNoThrow(httpContext); + var newCookieToken = ValidateAndGenerateNewToken(oldCookieToken); + + SaveCookieTokenAndHeader(httpContext, newCookieToken); + } + + // This method returns null if oldCookieToken is valid. + private AntiForgeryToken ValidateAndGenerateNewToken(AntiForgeryToken oldCookieToken) + { + if (!_validator.IsCookieTokenValid(oldCookieToken)) + { + // Need to make sure we're always operating with a good cookie token. + var newCookieToken = _generator.GenerateCookieToken(); + Debug.Assert(_validator.IsCookieTokenValid(newCookieToken)); + return newCookieToken; + } + + return null; + } + + private void SaveCookieTokenAndHeader( + [NotNull] HttpContext httpContext, + AntiForgeryToken newCookieToken) + { + if (newCookieToken != null) + { + // Persist the new cookie if it is not null. + _tokenStore.SaveCookieToken(httpContext, newCookieToken); + } + + if (!_config.SuppressXFrameOptionsHeader) + { + // Adding X-Frame-Options header to prevent ClickJacking. See + // http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10 + // for more information. + httpContext.Response.Headers.Set("X-Frame-Options", "SAMEORIGIN"); + } + } + private class AntiForgeryTokenSetInternal { public AntiForgeryToken FormToken { get; set; } diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index ccbcda94af..630510435b 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -561,7 +561,10 @@ namespace Microsoft.AspNet.Mvc.Razor /// A that represents the asynchronous flush operation and on /// completion returns a . /// The value returned is a token value that allows FlushAsync to work directly in an HTML - /// section. However the value does not represent the rendered content. + /// section. However the value does not represent the rendered content. + /// This method also writes out headers, so any modifications to headers must be done before FulshAsync is + /// called. For example, call to send anti-forgery cookie token + /// and X-Frame-Options header to client before this method flushes headers out. public async Task FlushAsync() { // If there are active writing scopes then we should throw. Cannot flush content that has the potential to @@ -617,6 +620,20 @@ namespace Microsoft.AspNet.Mvc.Razor PageExecutionContext?.EndContext(); } + /// + /// Sets anti-forgery cookie and X-Frame-Options header on the response. + /// + /// A that returns a . + /// Call this method to send anti-forgery cookie token and X-Frame-Options header to client + /// before flushes the headers. + public virtual HtmlString SetAntiForgeryCookieAndHeader() + { + var antiForgery = Context.RequestServices.GetRequiredService(); + antiForgery.SetCookieTokenAndHeader(Context); + + return HtmlString.Empty; + } + private void EnsureMethodCanBeInvoked(string methodName) { if (PreviousSectionWriters == null) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs index ba111c3711..70f59312cf 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs @@ -379,6 +379,29 @@ namespace Microsoft.AspNet.Mvc.Core.Test context.TokenProvider.Verify(); } + [Theory] + [InlineData(false, "SAMEORIGIN")] + [InlineData(true, null)] + public void SetCookieTokenAndHeader_AddsXFrameOptionsHeader(bool suppressXFrameOptions, string expectedHeaderValue) + { + // Arrange + var options = new AntiForgeryOptions() + { + SuppressXFrameOptionsHeader = suppressXFrameOptions + }; + + // Genreate a new cookie. + var context = GetAntiForgeryWorkerContext(options, useOldCookie: false, isOldCookieValid: false); + var worker = GetAntiForgeryWorker(context); + + // Act + worker.SetCookieTokenAndHeader(context.HttpContext.Object); + + // Assert + var xFrameOptions = context.HttpContext.Object.Response.Headers["X-Frame-Options"]; + Assert.Equal(expectedHeaderValue, xFrameOptions); + } + private AntiForgeryWorker GetAntiForgeryWorker(AntiForgeryWorkerContext context) { return new AntiForgeryWorker( diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs index 4a19cbe97a..f0c6402499 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs @@ -234,5 +234,117 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("The required anti-forgery form field \"__RequestVerificationToken\" is not present.", exception.ExceptionMessage); } + + [Fact] + public async Task SetCookieAndHeaderBeforeFlushAsync_GeneratesCookieTokenAndHeader() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/Account/FlushAsyncLogin"); + + // Assert + var header = Assert.Single(response.Headers.GetValues("X-Frame-Options")); + Assert.Equal("SAMEORIGIN", header); + + var setCookieHeader = response.Headers.GetValues("Set-Cookie").ToArray(); + + var cookie = Assert.Single(setCookieHeader); + Assert.True(cookie.StartsWith("__RequestVerificationToken")); + } + + [Fact] + public async Task SetCookieAndHeaderBeforeFlushAsync_PostToForm() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // do a get response. + var getResponse = await client.GetAsync("http://localhost/Account/FlushAsyncLogin"); + var resposneBody = await getResponse.Content.ReadAsStringAsync(); + + var formToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken(resposneBody, "Account/FlushAsyncLogin"); + var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); + + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/FlushAsyncLogin"); + request.Headers.Add("Cookie", "__RequestVerificationToken=" + cookieToken); + var nameValueCollection = new List> + { + new KeyValuePair("__RequestVerificationToken", formToken), + new KeyValuePair("UserName", "test"), + new KeyValuePair("Password", "password"), + }; + + request.Content = new FormUrlEncodedContent(nameValueCollection); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("OK", await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task FlushAsyncBeforeAntiForgery_CookieAndHeaderNotInResponse() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/Account/FlushWithoutUpdatingHeader"); + + // Assert + IEnumerable returnList; + Assert.False(response.Headers.TryGetValues("Set-Cookie", out returnList)); + Assert.False(response.Headers.TryGetValues("X-Frame-Options", out returnList)); + } + + [Fact] + public async Task FlushAsyncBeforeAntiForgery_PostToForm() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // do a get response. + var getResponse = await client.GetAsync("http://localhost/Account/FlushWithoutUpdatingHeader"); + var resposneBody = await getResponse.Content.ReadAsStringAsync(); + + // Assert - 1 + IEnumerable returnList; + Assert.False(getResponse.Headers.TryGetValues("Set-Cookie", out returnList)); + Assert.False(getResponse.Headers.TryGetValues("X-Frame-Options", out returnList)); + + var formToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken( + resposneBody, + "Account/FlushWithoutUpdatingHeader"); + + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/Account/FlushWithoutUpdatingHeader"); + + var nameValueCollection = new List> + { + new KeyValuePair("__RequestVerificationToken", formToken), + new KeyValuePair("UserName", "test"), + new KeyValuePair("Password", "password"), + }; + + request.Content = new FormUrlEncodedContent(nameValueCollection); + + // Act + var response = await client.SendAsync(request); + + // Assert - 2 + var exception = response.GetServerException(); + Assert.Equal("The required anti-forgery cookie \"__RequestVerificationToken\" is not present.", + exception.ExceptionMessage); + } + } } \ No newline at end of file diff --git a/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs b/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs index 701b421a39..1a32e742c5 100644 --- a/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs +++ b/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs @@ -31,5 +31,41 @@ namespace AntiForgeryWebSite { return "OK"; } + + // GET: /Account/FlushAsyncLogin + [AllowAnonymous] + public ActionResult FlushAsyncLogin(string returnUrl = null) + { + ViewBag.ReturnUrl = returnUrl; + + return View(); + } + + // POST: /Account/FlushAsyncLogin + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public string FlushAsyncLogin(LoginViewModel model) + { + return "OK"; + } + + // GET: /Account/FlushWithoutUpdatingHeader + [AllowAnonymous] + public ActionResult FlushWithoutUpdatingHeader(string returnUrl = null) + { + ViewBag.ReturnUrl = returnUrl; + + return View(); + } + + // POST: /Account/FlushWithoutUpdatingHeader + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public string FlushWithoutUpdatingHeader(LoginViewModel model) + { + return "OK"; + } } } \ No newline at end of file diff --git a/test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml b/test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml new file mode 100644 index 0000000000..65ee7aedd1 --- /dev/null +++ b/test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml @@ -0,0 +1,43 @@ +@model AntiForgeryWebSite.LoginViewModel + +@{ + ViewBag.Title = "Log in"; + Layout = "/Views/Shared/_FlushAsyncLayout.cshtml"; +} + +@section Login +{ +

@ViewBag.Title.

+
+
+
+ @using (Html.BeginForm("FlushAsyncLogin", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) + { + @Html.AntiForgeryToken() +

Use a local account to log in.

+
+ @Html.ValidationSummary(true) +
+ @Html.LabelFor(m => m.UserName, new { @class = "col-md-2", ClAsS = "col-md-2 control-label" }) +
+ @Html.TextBoxFor(m => m.UserName, new { @class = "...", cLass = "form-control" }) + @Html.ValidationMessageFor(m => m.UserName) +
+
+
+ @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) +
+ @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) + @Html.ValidationMessageFor(m => m.Password) +
+
+
+
+ +
+
+ } +
+
+
+} diff --git a/test/WebSites/AntiForgeryWebSite/Views/Account/FlushWithoutUpdatingHeader.cshtml b/test/WebSites/AntiForgeryWebSite/Views/Account/FlushWithoutUpdatingHeader.cshtml new file mode 100644 index 0000000000..97cf95a94e --- /dev/null +++ b/test/WebSites/AntiForgeryWebSite/Views/Account/FlushWithoutUpdatingHeader.cshtml @@ -0,0 +1,37 @@ +@model AntiForgeryWebSite.LoginViewModel + +@await FlushAsync() + +

@ViewBag.Title.

+
+
+
+ @using (Html.BeginForm("FlushWithoutUpdatingHeader", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) + { + @Html.AntiForgeryToken() +

Use a local account to log in.

+
+ @Html.ValidationSummary(true) +
+ @Html.LabelFor(m => m.UserName, new { @class = "col-md-2", ClAsS = "col-md-2 control-label" }) +
+ @Html.TextBoxFor(m => m.UserName, new { @class = "...", cLass = "form-control" }) + @Html.ValidationMessageFor(m => m.UserName) +
+
+
+ @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) +
+ @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) + @Html.ValidationMessageFor(m => m.Password) +
+
+
+
+ +
+
+ } +
+
+
diff --git a/test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml b/test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml new file mode 100644 index 0000000000..08c9240c47 --- /dev/null +++ b/test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml @@ -0,0 +1,15 @@ + + + @ViewBag.Title – AntiForgery Functional Tests + +@SetAntiForgeryCookieAndHeader() +@await FlushAsync() + + + @Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" }) +
+ @RenderBody() + @await RenderSectionAsync("Login", required: false) +
+ + \ No newline at end of file From c8a13087a612371b4fafe4caf25097b2e1a3c8c9 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 20 Jan 2015 00:49:15 -0800 Subject: [PATCH 103/118] Revert "Added SetAntiForgeryCookieAndHeader method that sets cookie token and header" This reverts commit 951ed05893d10beafbbe6449652684c19f2384ae. --- .../AntiForgery/AntiForgery.cs | 9 -- .../AntiForgery/AntiForgeryWorker.cs | 71 +++-------- src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs | 19 +-- .../AntiXsrf/AntiForgeryWorkerTest.cs | 23 ---- .../AntiForgeryTests.cs | 112 ------------------ .../Controllers/AccountController.cs | 36 ------ .../Views/Account/FlushAsyncLogin.cshtml | 43 ------- .../Account/FlushWithoutUpdatingHeader.cshtml | 37 ------ .../Views/Shared/_FlushAsyncLayout.cshtml | 15 --- 9 files changed, 20 insertions(+), 345 deletions(-) delete mode 100644 test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml delete mode 100644 test/WebSites/AntiForgeryWebSite/Views/Account/FlushWithoutUpdatingHeader.cshtml delete mode 100644 test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs index aedab3ceb5..644ee99287 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs @@ -97,14 +97,5 @@ namespace Microsoft.AspNet.Mvc { Validate(context, antiForgeryTokenSet.CookieToken, antiForgeryTokenSet.FormToken); } - - /// - /// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers. - /// - /// The HTTP context associated with the current call. - public void SetCookieTokenAndHeader([NotNull] HttpContext context) - { - _worker.SetCookieTokenAndHeader(context); - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs index 564fef4064..a2056fe0e3 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs @@ -102,8 +102,19 @@ namespace Microsoft.AspNet.Mvc var tokenSet = GetTokens(httpContext, oldCookieToken); var newCookieToken = tokenSet.CookieToken; var formToken = tokenSet.FormToken; + if (newCookieToken != null) + { + // If a new cookie was generated, persist it. + _tokenStore.SaveCookieToken(httpContext, newCookieToken); + } - SaveCookieTokenAndHeader(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"); + } // var retVal = new TagBuilder("input"); @@ -132,11 +143,15 @@ namespace Microsoft.AspNet.Mvc private AntiForgeryTokenSetInternal GetTokens(HttpContext httpContext, AntiForgeryToken oldCookieToken) { - var newCookieToken = ValidateAndGenerateNewToken(oldCookieToken); - if (newCookieToken != null) + AntiForgeryToken newCookieToken = null; + if (!_validator.IsCookieTokenValid(oldCookieToken)) { - oldCookieToken = newCookieToken; + // Need to make sure we're always operating with a good cookie token. + oldCookieToken = newCookieToken = _generator.GenerateCookieToken(); } + + Debug.Assert(_validator.IsCookieTokenValid(oldCookieToken)); + var formToken = _generator.GenerateFormToken( httpContext, ExtractIdentity(httpContext), @@ -189,54 +204,6 @@ namespace Microsoft.AspNet.Mvc deserializedFormToken); } - - /// - /// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers. - /// - /// The HTTP context associated with the current call. - public void SetCookieTokenAndHeader([NotNull] HttpContext httpContext) - { - CheckSSLConfig(httpContext); - - var oldCookieToken = GetCookieTokenNoThrow(httpContext); - var newCookieToken = ValidateAndGenerateNewToken(oldCookieToken); - - SaveCookieTokenAndHeader(httpContext, newCookieToken); - } - - // This method returns null if oldCookieToken is valid. - private AntiForgeryToken ValidateAndGenerateNewToken(AntiForgeryToken oldCookieToken) - { - if (!_validator.IsCookieTokenValid(oldCookieToken)) - { - // Need to make sure we're always operating with a good cookie token. - var newCookieToken = _generator.GenerateCookieToken(); - Debug.Assert(_validator.IsCookieTokenValid(newCookieToken)); - return newCookieToken; - } - - return null; - } - - private void SaveCookieTokenAndHeader( - [NotNull] HttpContext httpContext, - AntiForgeryToken newCookieToken) - { - if (newCookieToken != null) - { - // Persist the new cookie if it is not null. - _tokenStore.SaveCookieToken(httpContext, newCookieToken); - } - - if (!_config.SuppressXFrameOptionsHeader) - { - // Adding X-Frame-Options header to prevent ClickJacking. See - // http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10 - // for more information. - httpContext.Response.Headers.Set("X-Frame-Options", "SAMEORIGIN"); - } - } - private class AntiForgeryTokenSetInternal { public AntiForgeryToken FormToken { get; set; } diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index 630510435b..ccbcda94af 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -561,10 +561,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// A that represents the asynchronous flush operation and on /// completion returns a . /// The value returned is a token value that allows FlushAsync to work directly in an HTML - /// section. However the value does not represent the rendered content. - /// This method also writes out headers, so any modifications to headers must be done before FulshAsync is - /// called. For example, call to send anti-forgery cookie token - /// and X-Frame-Options header to client before this method flushes headers out. + /// section. However the value does not represent the rendered content. public async Task FlushAsync() { // If there are active writing scopes then we should throw. Cannot flush content that has the potential to @@ -620,20 +617,6 @@ namespace Microsoft.AspNet.Mvc.Razor PageExecutionContext?.EndContext(); } - /// - /// Sets anti-forgery cookie and X-Frame-Options header on the response. - /// - /// A that returns a . - /// Call this method to send anti-forgery cookie token and X-Frame-Options header to client - /// before flushes the headers. - public virtual HtmlString SetAntiForgeryCookieAndHeader() - { - var antiForgery = Context.RequestServices.GetRequiredService(); - antiForgery.SetCookieTokenAndHeader(Context); - - return HtmlString.Empty; - } - private void EnsureMethodCanBeInvoked(string methodName) { if (PreviousSectionWriters == null) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs index 70f59312cf..ba111c3711 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs @@ -379,29 +379,6 @@ namespace Microsoft.AspNet.Mvc.Core.Test context.TokenProvider.Verify(); } - [Theory] - [InlineData(false, "SAMEORIGIN")] - [InlineData(true, null)] - public void SetCookieTokenAndHeader_AddsXFrameOptionsHeader(bool suppressXFrameOptions, string expectedHeaderValue) - { - // Arrange - var options = new AntiForgeryOptions() - { - SuppressXFrameOptionsHeader = suppressXFrameOptions - }; - - // Genreate a new cookie. - var context = GetAntiForgeryWorkerContext(options, useOldCookie: false, isOldCookieValid: false); - var worker = GetAntiForgeryWorker(context); - - // Act - worker.SetCookieTokenAndHeader(context.HttpContext.Object); - - // Assert - var xFrameOptions = context.HttpContext.Object.Response.Headers["X-Frame-Options"]; - Assert.Equal(expectedHeaderValue, xFrameOptions); - } - private AntiForgeryWorker GetAntiForgeryWorker(AntiForgeryWorkerContext context) { return new AntiForgeryWorker( diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs index f0c6402499..4a19cbe97a 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs @@ -234,117 +234,5 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("The required anti-forgery form field \"__RequestVerificationToken\" is not present.", exception.ExceptionMessage); } - - [Fact] - public async Task SetCookieAndHeaderBeforeFlushAsync_GeneratesCookieTokenAndHeader() - { - // Arrange - var server = TestServer.Create(_services, _app); - var client = server.CreateClient(); - - // Act - var response = await client.GetAsync("http://localhost/Account/FlushAsyncLogin"); - - // Assert - var header = Assert.Single(response.Headers.GetValues("X-Frame-Options")); - Assert.Equal("SAMEORIGIN", header); - - var setCookieHeader = response.Headers.GetValues("Set-Cookie").ToArray(); - - var cookie = Assert.Single(setCookieHeader); - Assert.True(cookie.StartsWith("__RequestVerificationToken")); - } - - [Fact] - public async Task SetCookieAndHeaderBeforeFlushAsync_PostToForm() - { - // Arrange - var server = TestServer.Create(_services, _app); - var client = server.CreateClient(); - - // do a get response. - var getResponse = await client.GetAsync("http://localhost/Account/FlushAsyncLogin"); - var resposneBody = await getResponse.Content.ReadAsStringAsync(); - - var formToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken(resposneBody, "Account/FlushAsyncLogin"); - var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); - - var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/FlushAsyncLogin"); - request.Headers.Add("Cookie", "__RequestVerificationToken=" + cookieToken); - var nameValueCollection = new List> - { - new KeyValuePair("__RequestVerificationToken", formToken), - new KeyValuePair("UserName", "test"), - new KeyValuePair("Password", "password"), - }; - - request.Content = new FormUrlEncodedContent(nameValueCollection); - - // Act - var response = await client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("OK", await response.Content.ReadAsStringAsync()); - } - - [Fact] - public async Task FlushAsyncBeforeAntiForgery_CookieAndHeaderNotInResponse() - { - // Arrange - var server = TestServer.Create(_services, _app); - var client = server.CreateClient(); - - // Act - var response = await client.GetAsync("http://localhost/Account/FlushWithoutUpdatingHeader"); - - // Assert - IEnumerable returnList; - Assert.False(response.Headers.TryGetValues("Set-Cookie", out returnList)); - Assert.False(response.Headers.TryGetValues("X-Frame-Options", out returnList)); - } - - [Fact] - public async Task FlushAsyncBeforeAntiForgery_PostToForm() - { - // Arrange - var server = TestServer.Create(_services, _app); - var client = server.CreateClient(); - - // do a get response. - var getResponse = await client.GetAsync("http://localhost/Account/FlushWithoutUpdatingHeader"); - var resposneBody = await getResponse.Content.ReadAsStringAsync(); - - // Assert - 1 - IEnumerable returnList; - Assert.False(getResponse.Headers.TryGetValues("Set-Cookie", out returnList)); - Assert.False(getResponse.Headers.TryGetValues("X-Frame-Options", out returnList)); - - var formToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken( - resposneBody, - "Account/FlushWithoutUpdatingHeader"); - - var request = new HttpRequestMessage( - HttpMethod.Post, - "http://localhost/Account/FlushWithoutUpdatingHeader"); - - var nameValueCollection = new List> - { - new KeyValuePair("__RequestVerificationToken", formToken), - new KeyValuePair("UserName", "test"), - new KeyValuePair("Password", "password"), - }; - - request.Content = new FormUrlEncodedContent(nameValueCollection); - - // Act - var response = await client.SendAsync(request); - - // Assert - 2 - var exception = response.GetServerException(); - Assert.Equal("The required anti-forgery cookie \"__RequestVerificationToken\" is not present.", - exception.ExceptionMessage); - } - } } \ No newline at end of file diff --git a/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs b/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs index 1a32e742c5..701b421a39 100644 --- a/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs +++ b/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs @@ -31,41 +31,5 @@ namespace AntiForgeryWebSite { return "OK"; } - - // GET: /Account/FlushAsyncLogin - [AllowAnonymous] - public ActionResult FlushAsyncLogin(string returnUrl = null) - { - ViewBag.ReturnUrl = returnUrl; - - return View(); - } - - // POST: /Account/FlushAsyncLogin - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public string FlushAsyncLogin(LoginViewModel model) - { - return "OK"; - } - - // GET: /Account/FlushWithoutUpdatingHeader - [AllowAnonymous] - public ActionResult FlushWithoutUpdatingHeader(string returnUrl = null) - { - ViewBag.ReturnUrl = returnUrl; - - return View(); - } - - // POST: /Account/FlushWithoutUpdatingHeader - [HttpPost] - [AllowAnonymous] - [ValidateAntiForgeryToken] - public string FlushWithoutUpdatingHeader(LoginViewModel model) - { - return "OK"; - } } } \ No newline at end of file diff --git a/test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml b/test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml deleted file mode 100644 index 65ee7aedd1..0000000000 --- a/test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml +++ /dev/null @@ -1,43 +0,0 @@ -@model AntiForgeryWebSite.LoginViewModel - -@{ - ViewBag.Title = "Log in"; - Layout = "/Views/Shared/_FlushAsyncLayout.cshtml"; -} - -@section Login -{ -

@ViewBag.Title.

-
-
-
- @using (Html.BeginForm("FlushAsyncLogin", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) - { - @Html.AntiForgeryToken() -

Use a local account to log in.

-
- @Html.ValidationSummary(true) -
- @Html.LabelFor(m => m.UserName, new { @class = "col-md-2", ClAsS = "col-md-2 control-label" }) -
- @Html.TextBoxFor(m => m.UserName, new { @class = "...", cLass = "form-control" }) - @Html.ValidationMessageFor(m => m.UserName) -
-
-
- @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) -
- @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) - @Html.ValidationMessageFor(m => m.Password) -
-
-
-
- -
-
- } -
-
-
-} diff --git a/test/WebSites/AntiForgeryWebSite/Views/Account/FlushWithoutUpdatingHeader.cshtml b/test/WebSites/AntiForgeryWebSite/Views/Account/FlushWithoutUpdatingHeader.cshtml deleted file mode 100644 index 97cf95a94e..0000000000 --- a/test/WebSites/AntiForgeryWebSite/Views/Account/FlushWithoutUpdatingHeader.cshtml +++ /dev/null @@ -1,37 +0,0 @@ -@model AntiForgeryWebSite.LoginViewModel - -@await FlushAsync() - -

@ViewBag.Title.

-
-
-
- @using (Html.BeginForm("FlushWithoutUpdatingHeader", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) - { - @Html.AntiForgeryToken() -

Use a local account to log in.

-
- @Html.ValidationSummary(true) -
- @Html.LabelFor(m => m.UserName, new { @class = "col-md-2", ClAsS = "col-md-2 control-label" }) -
- @Html.TextBoxFor(m => m.UserName, new { @class = "...", cLass = "form-control" }) - @Html.ValidationMessageFor(m => m.UserName) -
-
-
- @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) -
- @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) - @Html.ValidationMessageFor(m => m.Password) -
-
-
-
- -
-
- } -
-
-
diff --git a/test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml b/test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml deleted file mode 100644 index 08c9240c47..0000000000 --- a/test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml +++ /dev/null @@ -1,15 +0,0 @@ - - - @ViewBag.Title – AntiForgery Functional Tests - -@SetAntiForgeryCookieAndHeader() -@await FlushAsync() - - - @Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" }) -
- @RenderBody() - @await RenderSectionAsync("Login", required: false) -
- - \ No newline at end of file From 9d6915839fa86de9cbfdf72eb3deaa7d0b138dd3 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 20 Jan 2015 01:32:19 -0800 Subject: [PATCH 104/118] Updating build.cmd and build.sh to use dotnetsdk --- build.cmd | 6 +++--- build.sh | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.cmd b/build.cmd index 86ca5bbbf1..c8041fdd9d 100644 --- a/build.cmd +++ b/build.cmd @@ -20,9 +20,9 @@ IF EXIST packages\KoreBuild goto run .nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion IF "%SKIP_KRE_INSTALL%"=="1" goto run -CALL packages\KoreBuild\build\kvm upgrade -runtime CLR -x86 -CALL packages\KoreBuild\build\kvm install default -runtime CoreCLR -x86 +CALL packages\KoreBuild\build\dotnetsdk upgrade -runtime CLR -x86 +CALL packages\KoreBuild\build\dotnetsdk install default -runtime CoreCLR -x86 :run -CALL packages\KoreBuild\build\kvm use default -runtime CLR -x86 +CALL packages\KoreBuild\build\dotnetsdk use default -runtime CLR -x86 packages\Sake\tools\Sake.exe -I packages\KoreBuild\build -f makefile.shade %* diff --git a/build.sh b/build.sh index c7873ef58e..3f3c731c04 100755 --- a/build.sh +++ b/build.sh @@ -28,11 +28,11 @@ if test ! -d packages/KoreBuild; then fi if ! type k > /dev/null 2>&1; then - source packages/KoreBuild/build/kvm.sh + source setup/dotnetsdk.sh fi if ! type k > /dev/null 2>&1; then - kvm upgrade + dotnetsdk upgrade fi mono packages/Sake/tools/Sake.exe -I packages/KoreBuild/build -f makefile.shade "$@" From a2d12669a8c6cdc854c3ffb71298c99501e7d592 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 20 Jan 2015 01:36:23 -0800 Subject: [PATCH 105/118] Updating build.cmd and build.sh to use dotnetsdk --- build.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.sh b/build.sh index 3f3c731c04..350d7e389a 100755 --- a/build.sh +++ b/build.sh @@ -28,7 +28,7 @@ if test ! -d packages/KoreBuild; then fi if ! type k > /dev/null 2>&1; then - source setup/dotnetsdk.sh + source packages/KoreBuild/build/dotnetsdk.sh fi if ! type k > /dev/null 2>&1; then From eda4b16cc5dfc11528116c51e3a119c1b513e986 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Mon, 19 Jan 2015 17:18:12 -0800 Subject: [PATCH 106/118] [Fixes #1836]SupportedMediaTypes for output formatters are incorrectly updated with charset data during requests --- .../Formatters/OutputFormatter.cs | 3 ++ .../Formatters/OutputFormatterTests.cs | 39 +++++++++++++++- .../ConnegTests.cs | 44 +++++++++++++++++++ .../Controllers/HomeController.cs | 5 +++ test/WebSites/ConnegWebSite/Startup.cs | 8 +++- 5 files changed, 97 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs index e08443c6b0..81ef066f9d 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs @@ -177,6 +177,9 @@ namespace Microsoft.AspNet.Mvc throw new InvalidOperationException(Resources.FormatOutputFormatterNoMediaType(GetType().FullName)); } + // Clone the media type as we don't want it to affect the next request + selectedMediaType = MediaTypeHeaderValue.Parse(selectedMediaType.ToString()); + var selectedEncoding = SelectCharacterEncoding(context); if (selectedEncoding == null) { diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs index e2e7cbe1f1..80e90c72d7 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/OutputFormatterTests.cs @@ -120,6 +120,29 @@ namespace Microsoft.AspNet.Mvc.Test formatterContext.SelectedContentType.ToString()); } + [Fact] + public async Task WriteResponseHeaders_ClonesMediaType() + { + // Arrange + var formatter = new PngImageFormatter(); + formatter.SupportedMediaTypes.Clear(); + var mediaType = new MediaTypeHeaderValue("image/png"); + formatter.SupportedMediaTypes.Add(mediaType); + var formatterContext = new OutputFormatterContext(); + formatterContext.ActionContext = new ActionContext( + new DefaultHttpContext(), + new RouteData(), + new ActionDescriptor()); + + // Act + await formatter.WriteAsync(formatterContext); + + // Assert + Assert.NotSame(mediaType, formatterContext.SelectedContentType); + Assert.Null(mediaType.Charset); + Assert.Equal("image/png; charset=utf-8", formatterContext.SelectedContentType.ToString()); + } + [Fact] public void CanWriteResult_ForNullContentType_UsesFirstEntryInSupportedContentTypes() { @@ -294,7 +317,7 @@ namespace Microsoft.AspNet.Mvc.Test public override bool CanWriteResult(OutputFormatterContext context, MediaTypeHeaderValue contentType) { // Do not set the selected media Type. - // The WriteResponseContentHeader should do it for you. + // The WriteResponseHeaders should do it for you. return true; } @@ -303,5 +326,19 @@ namespace Microsoft.AspNet.Mvc.Test return Task.FromResult(true); } } + + private class PngImageFormatter : OutputFormatter + { + public PngImageFormatter() + { + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("image/png")); + SupportedEncodings.Add(Encoding.UTF8); + } + + public override Task WriteResponseBodyAsync([NotNull] OutputFormatterContext context) + { + return Task.FromResult(true); + } + } } } diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs index d9c3f0e892..cadcbeb8d9 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ConnegTests.cs @@ -296,6 +296,50 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(expectedBody, body); } + [Fact] + public async Task JsonFormatter_SupportedMediaType_DoesNotChangeAcrossRequests() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + var expectedContentType = MediaTypeHeaderValue.Parse("application/json;charset=utf-8"); + var expectedBody = "{\"MethodName\":\"ReturnJsonResult\"}"; + + for (int i = 0; i < 5; i++) + { + // Act and Assert + var response = await client.GetAsync("http://localhost/JsonResult/ReturnJsonResult"); + + Assert.Equal(expectedContentType, response.Content.Headers.ContentType); + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedBody, body); + } + } + + [Fact] + public async Task XmlFormatter_SupportedMediaType_DoesNotChangeAcrossRequests() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/xml")); + client.DefaultRequestHeaders.AcceptCharset.Add(new StringWithQualityHeaderValue("utf-8")); + var expectedContentType = MediaTypeHeaderValue.Parse("application/xml;charset=utf-8"); + var expectedBody = @"
" + + @"One Microsoft Way
John
"; + + for (int i = 0; i < 5; i++) + { + // Act and Assert + var response = await client.GetAsync("http://localhost/Home/UserInfo"); + + Assert.Equal(expectedContentType, response.Content.Headers.ContentType); + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedBody, body); + } + } + [Theory] [InlineData("UseTheFallback_WithDefaultFormatters")] [InlineData("UseTheFallback_UsingCustomFormatters")] diff --git a/test/WebSites/ConnegWebSite/Controllers/HomeController.cs b/test/WebSites/ConnegWebSite/Controllers/HomeController.cs index 56307aa2fe..e54e8e8ad0 100644 --- a/test/WebSites/ConnegWebSite/Controllers/HomeController.cs +++ b/test/WebSites/ConnegWebSite/Controllers/HomeController.cs @@ -11,5 +11,10 @@ namespace ConnegWebSite { return new JsonResult("Index Method"); } + + public User UserInfo() + { + return new User() { Name = "John", Address = "One Microsoft Way" }; + } } } \ No newline at end of file diff --git a/test/WebSites/ConnegWebSite/Startup.cs b/test/WebSites/ConnegWebSite/Startup.cs index 8901654343..fd4e143ecf 100644 --- a/test/WebSites/ConnegWebSite/Startup.cs +++ b/test/WebSites/ConnegWebSite/Startup.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; @@ -18,8 +19,13 @@ namespace ConnegWebSite { // Add MVC services to the services container services.AddMvc(configuration); - }); + services.Configure(options => + { + options.AddXmlDataContractSerializerFormatter(); + }); + }); + // Add MVC to the request pipeline app.UseMvc(routes => { From 12243e7d97b50c4235c800d9a1ce94641cdf7d1c Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 8 Jan 2015 10:07:13 -0800 Subject: [PATCH 107/118] * Modifying RazorPrecompiler to use IMemoryCache to cache results when precompilation is invoked via Design Time Host. * Change compilation to occur in parallel --- .../PrecompilationCacheEntry.cs | 71 +++++++++ .../RazorFileInfoCollectionGenerator.cs | 9 +- .../Razor/PreCompileViews/RazorPreCompiler.cs | 144 +++++++++++------- src/Microsoft.AspNet.Mvc.Razor/project.json | 9 +- .../RazorPreCompileModule.cs | 8 +- 5 files changed, 183 insertions(+), 58 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/PrecompilationCacheEntry.cs diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/PrecompilationCacheEntry.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/PrecompilationCacheEntry.cs new file mode 100644 index 0000000000..e7a53d2509 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/PrecompilationCacheEntry.cs @@ -0,0 +1,71 @@ +// 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.CodeAnalysis; + +namespace Microsoft.AspNet.Mvc.Razor +{ + /// + /// An entry in the cache used by . + /// + public class PrecompilationCacheEntry + { + /// + /// Initializes a new instance of for a successful parse. + /// + /// The of the file being cached. + /// The to cache. + public PrecompilationCacheEntry([NotNull] RazorFileInfo fileInfo, + [NotNull] SyntaxTree syntaxTree) + { + FileInfo = fileInfo; + SyntaxTree = syntaxTree; + } + + /// + /// Initializes a new instance of for a failed parse. + /// + /// The produced from parsing the Razor + /// file. This does not contain s produced from compiling the parsed + /// . + public PrecompilationCacheEntry([NotNull] IReadOnlyList diagnostics) + { + Diagnostics = diagnostics; + } + + /// + /// Gets the associated with this cache entry instance. + /// + /// + /// This property is not null if is true. + /// + public RazorFileInfo FileInfo { get; } + + /// + /// Gets the produced from parsing the Razor file. + /// + /// + /// This property is not null if is true. + /// + public SyntaxTree SyntaxTree { get; } + + /// + /// Gets the s produced from parsing the generated contents of the file + /// specified by . This does not contain s produced from + /// compiling the parsed . + /// + /// + /// This property is null if is true. + /// + public IReadOnlyList Diagnostics { get; } + + /// + /// Gets a value that indicates if parsing was successful. + /// + public bool Success + { + get { return SyntaxTree != null; } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs index 5dc2b51c4a..4db7acad5d 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs @@ -12,16 +12,17 @@ namespace Microsoft.AspNet.Mvc.Razor { private string _fileFormat; - protected IReadOnlyList FileInfos { get; private set; } - protected CompilationSettings CompilationSettings { get; } - - public RazorFileInfoCollectionGenerator([NotNull] IReadOnlyList fileInfos, + public RazorFileInfoCollectionGenerator([NotNull] IEnumerable fileInfos, [NotNull] CompilationSettings compilationSettings) { FileInfos = fileInfos; CompilationSettings = compilationSettings; } + protected IEnumerable FileInfos { get; } + + protected CompilationSettings CompilationSettings { get; } + public virtual SyntaxTree GenerateCollection() { var builder = new StringBuilder(); diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs index 0b59bc46a2..4bbe5b90a5 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs @@ -4,8 +4,11 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNet.FileSystems; -using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis; +using Microsoft.Framework.Cache.Memory; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.OptionsModel; using Microsoft.Framework.Runtime; @@ -17,37 +20,42 @@ namespace Microsoft.AspNet.Mvc.Razor { private readonly IServiceProvider _serviceProvider; private readonly IFileSystem _fileSystem; - private readonly IMvcRazorHost _host; public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider, + [NotNull] IMemoryCache precompilationCache, [NotNull] CompilationSettings compilationSettings) : this(designTimeServiceProvider, - designTimeServiceProvider.GetRequiredService(), designTimeServiceProvider.GetRequiredService>(), + precompilationCache, compilationSettings) { } public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider, - [NotNull] IMvcRazorHost host, [NotNull] IOptions optionsAccessor, + [NotNull] IMemoryCache precompilationCache, [NotNull] CompilationSettings compilationSettings) { _serviceProvider = designTimeServiceProvider; - _host = host; _fileSystem = optionsAccessor.Options.FileSystem; CompilationSettings = compilationSettings; + PreCompilationCache = precompilationCache; } protected CompilationSettings CompilationSettings { get; } + protected IMemoryCache PreCompilationCache { get; } + protected virtual string FileExtension { get; } = ".cshtml"; + protected virtual int MaxDegreesOfParallelism { get; } = Environment.ProcessorCount; + + public virtual void CompileViews([NotNull] IBeforeCompileContext context) { var descriptors = CreateCompilationDescriptors(context); - if (descriptors.Count > 0) + if (descriptors.Any()) { var collectionGenerator = new RazorFileInfoCollectionGenerator( descriptors, @@ -58,93 +66,127 @@ namespace Microsoft.AspNet.Mvc.Razor } } - protected virtual IReadOnlyList CreateCompilationDescriptors( - [NotNull] IBeforeCompileContext context) + protected virtual IEnumerable CreateCompilationDescriptors( + [NotNull] IBeforeCompileContext context) { - var list = new List(); + var filesToProcess = new List(); + GetFileInfosRecursive(root: string.Empty, razorFiles: filesToProcess); - foreach (var info in GetFileInfosRecursive(string.Empty)) + var razorFiles = new RazorFileInfo[filesToProcess.Count]; + var syntaxTrees = new SyntaxTree[filesToProcess.Count]; + var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = MaxDegreesOfParallelism }; + var diagnosticsLock = new object(); + + Parallel.For(0, filesToProcess.Count, parallelOptions, index => { - var descriptor = ParseView(info, context); - - if (descriptor != null) + var file = filesToProcess[index]; + var cacheEntry = PreCompilationCache.GetOrSet(file.RelativePath, + file, + OnCacheMiss); + if (cacheEntry != null) { - list.Add(descriptor); + if (cacheEntry.Success) + { + syntaxTrees[index] = cacheEntry.SyntaxTree; + razorFiles[index] = cacheEntry.FileInfo; + } + else + { + lock (diagnosticsLock) + { + foreach (var diagnostic in cacheEntry.Diagnostics) + { + context.Diagnostics.Add(diagnostic); + } + } + } + } + }); + + context.CSharpCompilation = context.CSharpCompilation + .AddSyntaxTrees(syntaxTrees.Where(tree => tree != null)); + return razorFiles.Where(file => file != null); + } + + protected IMvcRazorHost GetRazorHost() + { + return _serviceProvider.GetRequiredService(); + } + + private PrecompilationCacheEntry OnCacheMiss(ICacheSetContext cacheSetContext) + { + var fileInfo = (RelativeFileInfo)cacheSetContext.State; + var entry = GetCacheEntry(fileInfo); + + if (entry != null) + { + cacheSetContext.AddExpirationTrigger(_fileSystem.Watch(fileInfo.RelativePath)); + foreach (var viewStartPath in ViewStartUtility.GetViewStartLocations(fileInfo.RelativePath)) + { + cacheSetContext.AddExpirationTrigger(_fileSystem.Watch(viewStartPath)); } } - return list; + return entry; } - private IEnumerable GetFileInfosRecursive(string currentPath) + private void GetFileInfosRecursive(string root, List razorFiles) { - var path = currentPath; - - var fileInfos = _fileSystem.GetDirectoryContents(path); - if (!fileInfos.Exists) - { - yield break; - } + var fileInfos = _fileSystem.GetDirectoryContents(root); foreach (var fileInfo in fileInfos) { if (fileInfo.IsDirectory) { - var subPath = Path.Combine(path, fileInfo.Name); - - foreach (var info in GetFileInfosRecursive(subPath)) - { - yield return info; - } + var subPath = Path.Combine(root, fileInfo.Name); + GetFileInfosRecursive(subPath, razorFiles); } else if (Path.GetExtension(fileInfo.Name) .Equals(FileExtension, StringComparison.OrdinalIgnoreCase)) { - var relativePath = Path.Combine(currentPath, fileInfo.Name); + var relativePath = Path.Combine(root, fileInfo.Name); var info = new RelativeFileInfo(fileInfo, relativePath); - yield return info; + razorFiles.Add(info); } } } - protected virtual RazorFileInfo ParseView([NotNull] RelativeFileInfo fileInfo, - [NotNull] IBeforeCompileContext context) + protected virtual PrecompilationCacheEntry GetCacheEntry([NotNull] RelativeFileInfo fileInfo) { using (var stream = fileInfo.FileInfo.CreateReadStream()) { - var results = _host.GenerateCode(fileInfo.RelativePath, stream); + var host = GetRazorHost(); + var results = host.GenerateCode(fileInfo.RelativePath, stream); - foreach (var parserError in results.ParserErrors) + if (results.Success) { - var diagnostic = parserError.ToDiagnostics(fileInfo.FileInfo.PhysicalPath); - context.Diagnostics.Add(diagnostic); - } - - var generatedCode = results.GeneratedCode; - - if (generatedCode != null) - { - var syntaxTree = SyntaxTreeGenerator.Generate(generatedCode, + var syntaxTree = SyntaxTreeGenerator.Generate(results.GeneratedCode, fileInfo.FileInfo.PhysicalPath, CompilationSettings); - var fullTypeName = results.GetMainClassName(_host, syntaxTree); + var fullTypeName = results.GetMainClassName(host, syntaxTree); if (fullTypeName != null) { - context.CSharpCompilation = context.CSharpCompilation.AddSyntaxTrees(syntaxTree); - var hash = RazorFileHash.GetHash(fileInfo.FileInfo); - - return new RazorFileInfo() + var razorFileInfo = new RazorFileInfo { - FullTypeName = fullTypeName, RelativePath = fileInfo.RelativePath, LastModified = fileInfo.FileInfo.LastModified, Length = fileInfo.FileInfo.Length, - Hash = hash, + FullTypeName = fullTypeName }; + + return new PrecompilationCacheEntry(razorFileInfo, syntaxTree); } } + else + { + var diagnostics = results.ParserErrors + .Select(error => error.ToDiagnostics(fileInfo.FileInfo.PhysicalPath)) + .ToList(); + + return new PrecompilationCacheEntry(diagnostics); + } } return null; diff --git a/src/Microsoft.AspNet.Mvc.Razor/project.json b/src/Microsoft.AspNet.Mvc.Razor/project.json index 8b0ebed627..75da75289d 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/project.json +++ b/src/Microsoft.AspNet.Mvc.Razor/project.json @@ -9,7 +9,8 @@ "Microsoft.AspNet.Mvc.Core": "6.0.0-*", "Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*", "Microsoft.CodeAnalysis.CSharp": "1.0.0-rc1-*", - "Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-*" + "Microsoft.Framework.Cache.Memory": "1.0.0-*", + "Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-*" }, "frameworks": { "aspnet50": { @@ -21,6 +22,10 @@ "System.Threading.Tasks": "" } }, - "aspnetcore50": {} + "aspnetcore50": { + "dependencies": { + "System.Threading.Tasks.Parallel": "4.0.0-beta-*" + } + } } } diff --git a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs index d749009194..351f3b6b18 100644 --- a/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs +++ b/src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs @@ -9,6 +9,7 @@ using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Mvc.Razor; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; +using Microsoft.Framework.Cache.Memory; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Fallback; using Microsoft.Framework.DependencyInjection.ServiceLookup; @@ -19,10 +20,15 @@ namespace Microsoft.AspNet.Mvc public abstract class RazorPreCompileModule : ICompileModule { private readonly IServiceProvider _appServices; + private readonly IMemoryCache _memoryCache; public RazorPreCompileModule(IServiceProvider services) { _appServices = services; + // When ListenForMemoryPressure is true, the MemoryCache evicts items at every gen2 collection. + // In DTH, gen2 happens frequently enough to make it undesirable for caching precompilation results. We'll + // disable listening for memory pressure for the MemoryCache instance used by precompilation. + _memoryCache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false }); } protected virtual string FileExtension { get; } = ".cshtml"; @@ -39,7 +45,7 @@ namespace Microsoft.AspNet.Mvc sc.AddMvc(); var serviceProvider = BuildFallbackServiceProvider(sc, _appServices); - var viewCompiler = new RazorPreCompiler(serviceProvider, compilationSettings); + var viewCompiler = new RazorPreCompiler(serviceProvider, _memoryCache, compilationSettings); viewCompiler.CompileViews(context); } From 9299565706bf4a8bf9d86ce98e3dbccb6fc1b998 Mon Sep 17 00:00:00 2001 From: Kirthi Krishnamraju Date: Tue, 20 Jan 2015 10:46:54 -0800 Subject: [PATCH 108/118] Revert "Revert "Added SetAntiForgeryCookieAndHeader method that sets cookie token and header"" This reverts commit c8a13087a612371b4fafe4caf25097b2e1a3c8c9. --- .../AntiForgery/AntiForgery.cs | 9 +++ .../AntiForgery/AntiForgeryWorker.cs | 71 ++++++++++++++----- src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs | 19 ++++- .../AntiXsrf/AntiForgeryWorkerTest.cs | 23 ++++++ .../AntiForgeryTests.cs | 53 ++++++++++++++ .../Controllers/AccountController.cs | 18 +++++ .../Views/Account/FlushAsyncLogin.cshtml | 43 +++++++++++ .../Views/Shared/_FlushAsyncLayout.cshtml | 15 ++++ 8 files changed, 231 insertions(+), 20 deletions(-) create mode 100644 test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml create mode 100644 test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs index 644ee99287..aedab3ceb5 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgery.cs @@ -97,5 +97,14 @@ namespace Microsoft.AspNet.Mvc { Validate(context, antiForgeryTokenSet.CookieToken, antiForgeryTokenSet.FormToken); } + + /// + /// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers. + /// + /// The HTTP context associated with the current call. + public void SetCookieTokenAndHeader([NotNull] HttpContext context) + { + _worker.SetCookieTokenAndHeader(context); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs index a2056fe0e3..564fef4064 100644 --- a/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs +++ b/src/Microsoft.AspNet.Mvc.Core/AntiForgery/AntiForgeryWorker.cs @@ -102,19 +102,8 @@ namespace Microsoft.AspNet.Mvc var tokenSet = GetTokens(httpContext, oldCookieToken); var newCookieToken = tokenSet.CookieToken; var formToken = tokenSet.FormToken; - if (newCookieToken != null) - { - // If a new cookie was generated, persist it. - _tokenStore.SaveCookieToken(httpContext, newCookieToken); - } - if (!_config.SuppressXFrameOptionsHeader) - { - // Adding X-Frame-Options header to prevent ClickJacking. See - // http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10 - // for more information. - httpContext.Response.Headers.Set("X-Frame-Options", "SAMEORIGIN"); - } + SaveCookieTokenAndHeader(httpContext, newCookieToken); // var retVal = new TagBuilder("input"); @@ -143,15 +132,11 @@ namespace Microsoft.AspNet.Mvc private AntiForgeryTokenSetInternal GetTokens(HttpContext httpContext, AntiForgeryToken oldCookieToken) { - AntiForgeryToken newCookieToken = null; - if (!_validator.IsCookieTokenValid(oldCookieToken)) + var newCookieToken = ValidateAndGenerateNewToken(oldCookieToken); + if (newCookieToken != null) { - // Need to make sure we're always operating with a good cookie token. - oldCookieToken = newCookieToken = _generator.GenerateCookieToken(); + oldCookieToken = newCookieToken; } - - Debug.Assert(_validator.IsCookieTokenValid(oldCookieToken)); - var formToken = _generator.GenerateFormToken( httpContext, ExtractIdentity(httpContext), @@ -204,6 +189,54 @@ namespace Microsoft.AspNet.Mvc deserializedFormToken); } + + /// + /// Generates and sets an anti-forgery cookie if one is not available or not valid. Also sets response headers. + /// + /// The HTTP context associated with the current call. + public void SetCookieTokenAndHeader([NotNull] HttpContext httpContext) + { + CheckSSLConfig(httpContext); + + var oldCookieToken = GetCookieTokenNoThrow(httpContext); + var newCookieToken = ValidateAndGenerateNewToken(oldCookieToken); + + SaveCookieTokenAndHeader(httpContext, newCookieToken); + } + + // This method returns null if oldCookieToken is valid. + private AntiForgeryToken ValidateAndGenerateNewToken(AntiForgeryToken oldCookieToken) + { + if (!_validator.IsCookieTokenValid(oldCookieToken)) + { + // Need to make sure we're always operating with a good cookie token. + var newCookieToken = _generator.GenerateCookieToken(); + Debug.Assert(_validator.IsCookieTokenValid(newCookieToken)); + return newCookieToken; + } + + return null; + } + + private void SaveCookieTokenAndHeader( + [NotNull] HttpContext httpContext, + AntiForgeryToken newCookieToken) + { + if (newCookieToken != null) + { + // Persist the new cookie if it is not null. + _tokenStore.SaveCookieToken(httpContext, newCookieToken); + } + + if (!_config.SuppressXFrameOptionsHeader) + { + // Adding X-Frame-Options header to prevent ClickJacking. See + // http://tools.ietf.org/html/draft-ietf-websec-x-frame-options-10 + // for more information. + httpContext.Response.Headers.Set("X-Frame-Options", "SAMEORIGIN"); + } + } + private class AntiForgeryTokenSetInternal { public AntiForgeryToken FormToken { get; set; } diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs index ccbcda94af..630510435b 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPage.cs @@ -561,7 +561,10 @@ namespace Microsoft.AspNet.Mvc.Razor /// A that represents the asynchronous flush operation and on /// completion returns a . /// The value returned is a token value that allows FlushAsync to work directly in an HTML - /// section. However the value does not represent the rendered content. + /// section. However the value does not represent the rendered content. + /// This method also writes out headers, so any modifications to headers must be done before FulshAsync is + /// called. For example, call to send anti-forgery cookie token + /// and X-Frame-Options header to client before this method flushes headers out. public async Task FlushAsync() { // If there are active writing scopes then we should throw. Cannot flush content that has the potential to @@ -617,6 +620,20 @@ namespace Microsoft.AspNet.Mvc.Razor PageExecutionContext?.EndContext(); } + /// + /// Sets anti-forgery cookie and X-Frame-Options header on the response. + /// + /// A that returns a . + /// Call this method to send anti-forgery cookie token and X-Frame-Options header to client + /// before flushes the headers. + public virtual HtmlString SetAntiForgeryCookieAndHeader() + { + var antiForgery = Context.RequestServices.GetRequiredService(); + antiForgery.SetCookieTokenAndHeader(Context); + + return HtmlString.Empty; + } + private void EnsureMethodCanBeInvoked(string methodName) { if (PreviousSectionWriters == null) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs index ba111c3711..70f59312cf 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/AntiXsrf/AntiForgeryWorkerTest.cs @@ -379,6 +379,29 @@ namespace Microsoft.AspNet.Mvc.Core.Test context.TokenProvider.Verify(); } + [Theory] + [InlineData(false, "SAMEORIGIN")] + [InlineData(true, null)] + public void SetCookieTokenAndHeader_AddsXFrameOptionsHeader(bool suppressXFrameOptions, string expectedHeaderValue) + { + // Arrange + var options = new AntiForgeryOptions() + { + SuppressXFrameOptionsHeader = suppressXFrameOptions + }; + + // Genreate a new cookie. + var context = GetAntiForgeryWorkerContext(options, useOldCookie: false, isOldCookieValid: false); + var worker = GetAntiForgeryWorker(context); + + // Act + worker.SetCookieTokenAndHeader(context.HttpContext.Object); + + // Assert + var xFrameOptions = context.HttpContext.Object.Response.Headers["X-Frame-Options"]; + Assert.Equal(expectedHeaderValue, xFrameOptions); + } + private AntiForgeryWorker GetAntiForgeryWorker(AntiForgeryWorkerContext context) { return new AntiForgeryWorker( diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs index 4a19cbe97a..ff735002c8 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/AntiForgeryTests.cs @@ -234,5 +234,58 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("The required anti-forgery form field \"__RequestVerificationToken\" is not present.", exception.ExceptionMessage); } + + [Fact] + public async Task SetCookieAndHeaderBeforeFlushAsync_GeneratesCookieTokenAndHeader() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/Account/FlushAsyncLogin"); + + // Assert + var header = Assert.Single(response.Headers.GetValues("X-Frame-Options")); + Assert.Equal("SAMEORIGIN", header); + + var setCookieHeader = response.Headers.GetValues("Set-Cookie").ToArray(); + + var cookie = Assert.Single(setCookieHeader); + Assert.True(cookie.StartsWith("__RequestVerificationToken")); + } + + [Fact] + public async Task SetCookieAndHeaderBeforeFlushAsync_PostToForm() + { + // Arrange + var server = TestServer.Create(_services, _app); + var client = server.CreateClient(); + + // do a get response. + var getResponse = await client.GetAsync("http://localhost/Account/FlushAsyncLogin"); + var resposneBody = await getResponse.Content.ReadAsStringAsync(); + + var formToken = AntiForgeryTestHelper.RetrieveAntiForgeryToken(resposneBody, "Account/FlushAsyncLogin"); + var cookieToken = AntiForgeryTestHelper.RetrieveAntiForgeryCookie(getResponse); + + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Account/FlushAsyncLogin"); + request.Headers.Add("Cookie", "__RequestVerificationToken=" + cookieToken); + var nameValueCollection = new List> + { + new KeyValuePair("__RequestVerificationToken", formToken), + new KeyValuePair("UserName", "test"), + new KeyValuePair("Password", "password"), + }; + + request.Content = new FormUrlEncodedContent(nameValueCollection); + + // Act + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("OK", await response.Content.ReadAsStringAsync()); + } } } \ No newline at end of file diff --git a/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs b/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs index 701b421a39..a53852d897 100644 --- a/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs +++ b/test/WebSites/AntiForgeryWebSite/Controllers/AccountController.cs @@ -31,5 +31,23 @@ namespace AntiForgeryWebSite { return "OK"; } + + // GET: /Account/FlushAsyncLogin + [AllowAnonymous] + public ActionResult FlushAsyncLogin(string returnUrl = null) + { + ViewBag.ReturnUrl = returnUrl; + + return View(); + } + + // POST: /Account/FlushAsyncLogin + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + public string FlushAsyncLogin(LoginViewModel model) + { + return "OK"; + } } } \ No newline at end of file diff --git a/test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml b/test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml new file mode 100644 index 0000000000..65ee7aedd1 --- /dev/null +++ b/test/WebSites/AntiForgeryWebSite/Views/Account/FlushAsyncLogin.cshtml @@ -0,0 +1,43 @@ +@model AntiForgeryWebSite.LoginViewModel + +@{ + ViewBag.Title = "Log in"; + Layout = "/Views/Shared/_FlushAsyncLayout.cshtml"; +} + +@section Login +{ +

@ViewBag.Title.

+
+
+
+ @using (Html.BeginForm("FlushAsyncLogin", "Account", FormMethod.Post, new { @class = "form-horizontal", role = "form" })) + { + @Html.AntiForgeryToken() +

Use a local account to log in.

+
+ @Html.ValidationSummary(true) +
+ @Html.LabelFor(m => m.UserName, new { @class = "col-md-2", ClAsS = "col-md-2 control-label" }) +
+ @Html.TextBoxFor(m => m.UserName, new { @class = "...", cLass = "form-control" }) + @Html.ValidationMessageFor(m => m.UserName) +
+
+
+ @Html.LabelFor(m => m.Password, new { @class = "col-md-2 control-label" }) +
+ @Html.PasswordFor(m => m.Password, new { @class = "form-control" }) + @Html.ValidationMessageFor(m => m.Password) +
+
+
+
+ +
+
+ } +
+
+
+} diff --git a/test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml b/test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml new file mode 100644 index 0000000000..08c9240c47 --- /dev/null +++ b/test/WebSites/AntiForgeryWebSite/Views/Shared/_FlushAsyncLayout.cshtml @@ -0,0 +1,15 @@ + + + @ViewBag.Title – AntiForgery Functional Tests + +@SetAntiForgeryCookieAndHeader() +@await FlushAsync() + + + @Html.ActionLink("Log in", "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" }) +
+ @RenderBody() + @await RenderSectionAsync("Login", required: false) +
+ + \ No newline at end of file From 7667eba34edd587fdd23e135b6f09dad956e17bf Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 2 Dec 2014 10:22:08 -0800 Subject: [PATCH 109/118] Layouts for partials Fixes #1621 --- src/Microsoft.AspNet.Mvc.Razor/RazorView.cs | 30 +---- .../ViewEngineTests.cs | 120 +++++++++++++++++- .../RazorViewTest.cs | 28 +++- .../Components/ComponentWithLayout.cs | 16 +++ .../Components/ComponentWithViewStart.cs | 16 +++ .../PartialsWithLayoutController.cs | 31 +++++ .../Controllers/ViewEngineController.cs | 11 ++ .../LayoutForViewStartWithLayout.cshtml | 1 + .../PartialThatDoesNotSpecifyLayout.cshtml | 1 + .../PartialThatSpecifiesLayout.cshtml | 4 + .../PartialsRenderedViaPartialAsync.cshtml | 2 + .../PartialsRenderedViaRenderPartial.cshtml | 4 + .../PartialsWithLayout/_ViewStart.cshtml | 3 + .../ComponentWithLayout/Default.cshtml | 4 + .../ComponentWithViewStart/Default.cshtml | 1 + .../ComponentWithViewStart/_ViewStart.cshtml | 3 + .../Views/Shared/_ComponentLayout.cshtml | 2 + .../ViewWithComponentThatHasLayout.cshtml | 5 + .../ViewWithComponentThatHasViewStart.cshtml | 1 + 19 files changed, 245 insertions(+), 38 deletions(-) create mode 100644 test/WebSites/RazorWebSite/Components/ComponentWithLayout.cs create mode 100644 test/WebSites/RazorWebSite/Components/ComponentWithViewStart.cs create mode 100644 test/WebSites/RazorWebSite/Controllers/PartialsWithLayoutController.cs create mode 100644 test/WebSites/RazorWebSite/Views/PartialsWithLayout/LayoutForViewStartWithLayout.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatDoesNotSpecifyLayout.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatSpecifiesLayout.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaPartialAsync.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaRenderPartial.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/PartialsWithLayout/_ViewStart.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithLayout/Default.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/Default.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/_ViewStart.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/Shared/_ComponentLayout.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasLayout.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasViewStart.cshtml diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs index 1ed944a9a7..746c1e1313 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs @@ -62,32 +62,10 @@ namespace Microsoft.AspNet.Mvc.Razor { _pageExecutionFeature = context.HttpContext.GetFeature(); - if (IsPartial) - { - await RenderPartialAsync(context); - } - else - { - var bodyWriter = await RenderPageAsync(RazorPage, context, executeViewStart: true); - await RenderLayoutAsync(context, bodyWriter); - } - } - - private async Task RenderPartialAsync(ViewContext context) - { - if (EnableInstrumentation) - { - // When instrmenting, we need to Decorate the output in an instrumented writer which - // RenderPageAsync does. - var bodyWriter = await RenderPageAsync(RazorPage, context, executeViewStart: false); - await bodyWriter.CopyToAsync(context.Writer); - } - else - { - // For the non-instrumented case, we don't need to buffer contents. For Html.Partial, the writer is - // an in memory writer and for Partial views, we directly write to the Response. - await RenderPageCoreAsync(RazorPage, context); - } + // Partials don't execute _ViewStart pages, but may execute Layout pages if the Layout property + // is explicitly specified in the page. + var bodyWriter = await RenderPageAsync(RazorPage, context, executeViewStart: !IsPartial); + await RenderLayoutAsync(context, bodyWriter); } private async Task RenderPageAsync(IRazorPage page, diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs index f87b3899a4..29a5846e1b 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs @@ -164,25 +164,41 @@ component-content"; Assert.Equal(expected, body.Trim()); } - public static IEnumerable PartialRazorViews_DoNotRenderLayoutData + public static IEnumerable RazorViewEngine_RendersPartialViewsData { get { yield return new[] { - "ViewWithoutLayout", @"ViewWithoutLayout-Content" + "ViewWithoutLayout", "ViewWithoutLayout-Content" }; yield return new[] { - "PartialViewWithNamePassedIn", @"ViewWithLayout-Content" + "PartialViewWithNamePassedIn", +@" + +ViewWithLayout-Content +" }; yield return new[] { - "ViewWithFullPath", "ViewWithFullPath-content" + "ViewWithFullPath", +@" + +ViewWithFullPath-content +" }; yield return new[] { - "ViewWithNestedLayout", "ViewWithNestedLayout-Content" + "ViewWithNestedLayout", +@" + + +/PartialViewEngine/ViewWithNestedLayout + +ViewWithNestedLayout-Content + +" }; yield return new[] { @@ -199,8 +215,8 @@ component-content"; } [Theory] - [MemberData(nameof(PartialRazorViews_DoNotRenderLayoutData))] - public async Task PartialRazorViews_DoNotRenderLayout(string actionName, string expected) + [MemberData(nameof(RazorViewEngine_RendersPartialViewsData))] + public async Task RazorViewEngine_RendersPartialViews(string actionName, string expected) { // Arrange var server = TestServer.Create(_provider, _app); @@ -289,5 +305,95 @@ View With Layout // Assert Assert.Equal(expected, body.Trim()); } + + [Fact] + public async Task ViewComponentsExecuteLayout() + { + // Arrange + var expected = +@"View With Component With Layout + +Page Content +ViewComponent With Title + +Component With Layout"; + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var body = await client.GetStringAsync("http://localhost/ViewEngine/ViewWithComponentThatHasLayout"); + + // Assert + Assert.Equal(expected, body.Trim()); + } + + [Fact] + public async Task ViewComponentsDoNotExecuteViewStarts() + { + // Arrange + var expected = @"ViewComponent With ViewStart"; + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var body = await client.GetStringAsync("http://localhost/ViewEngine/ViewWithComponentThatHasViewStart"); + + // Assert + Assert.Equal(expected, body.Trim()); + } + + [Fact] + public async Task PartialDoNotExecuteViewStarts() + { + // Arrange + var expected = "Partial that does not specify Layout"; + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var body = await client.GetStringAsync("http://localhost/PartialsWithLayout/PartialDoesNotExecuteViewStarts"); + + // Assert + Assert.Equal(expected, body.Trim()); + } + + [Fact] + public async Task PartialsRenderedViaRenderPartialAsync_CanRenderLayouts() + { + // Arrange + var expected = +@" +Partial that specifies Layout +Partial that does not specify Layout +"; + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var body = await client.GetStringAsync("http://localhost/PartialsWithLayout/PartialsRenderedViaRenderPartial"); + + // Assert + Assert.Equal(expected, body.Trim()); + } + + [Fact] + public async Task PartialsRenderedViaPartialAsync_CanRenderLayouts() + { + // Arrange + var expected = +@" +Partial that specifies Layout + +Partial that does not specify Layout +"; + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var body = await client.GetStringAsync("http://localhost/PartialsWithLayout/PartialsRenderedViaPartialAsync"); + + // Assert + Assert.Equal(expected, body.Trim()); + } } } diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs index b107db2f8d..44c8864209 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Mvc.Razor #pragma warning restore 1998 [Fact] - public async Task RenderAsync_AsPartial_DoesNotBufferOutput() + public async Task RenderAsync_AsPartial_BuffersOutput() { // Arrange TextWriter actual = null; @@ -43,7 +43,8 @@ namespace Microsoft.AspNet.Mvc.Razor await view.RenderAsync(viewContext); // Assert - Assert.Same(expected, actual); + Assert.NotSame(expected, actual); + Assert.IsAssignableFrom(actual); Assert.Equal("Hello world", viewContext.Writer.ToString()); } @@ -105,13 +106,31 @@ namespace Microsoft.AspNet.Mvc.Razor } [Fact] - public async Task RenderAsync_AsPartial_DoesNotExecuteLayoutOrViewStartPages() + public async Task RenderAsync_AsPartial_ExecutesLayout_ButNotViewStartPages() { + // Arrange + var expected = string.Join(Environment.NewLine, + "layout-content", + "page-content"); var page = new TestableRazorPage(v => { v.Layout = LayoutPath; + v.Write("page-content"); }); + + var layout = new TestableRazorPage(v => + { + v.Write("layout-content" + Environment.NewLine); + v.RenderBodyPublic(); + }); + var pageFactory = new Mock(); + pageFactory.Setup(p => p.CreateInstance(LayoutPath)) + .Returns(layout); + var viewEngine = new Mock(); + viewEngine.Setup(v => v.FindPage(It.IsAny(), LayoutPath)) + .Returns(new RazorPageResult(LayoutPath, layout)); + var viewStartProvider = CreateViewStartProvider(); var view = new RazorView(viewEngine.Object, Mock.Of(), @@ -124,10 +143,9 @@ namespace Microsoft.AspNet.Mvc.Razor await view.RenderAsync(viewContext); // Assert - viewEngine.Verify(v => v.FindPage(It.IsAny(), It.IsAny()), - Times.Never()); Mock.Get(viewStartProvider) .Verify(v => v.GetViewStartPages(It.IsAny()), Times.Never()); + Assert.Equal(expected, viewContext.Writer.ToString()); } [Fact] diff --git a/test/WebSites/RazorWebSite/Components/ComponentWithLayout.cs b/test/WebSites/RazorWebSite/Components/ComponentWithLayout.cs new file mode 100644 index 0000000000..91acffc9b8 --- /dev/null +++ b/test/WebSites/RazorWebSite/Components/ComponentWithLayout.cs @@ -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 Microsoft.AspNet.Mvc; + +namespace MvcSample.Web.Components +{ + public class ComponentWithLayout : ViewComponent + { + public IViewComponentResult Invoke() + { + ViewData["Title"] = "ViewComponent With Title"; + return View(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Components/ComponentWithViewStart.cs b/test/WebSites/RazorWebSite/Components/ComponentWithViewStart.cs new file mode 100644 index 0000000000..0d34e55671 --- /dev/null +++ b/test/WebSites/RazorWebSite/Components/ComponentWithViewStart.cs @@ -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 Microsoft.AspNet.Mvc; + +namespace MvcSample.Web.Components +{ + public class ComponentWithViewStart : ViewComponent + { + public IViewComponentResult Invoke() + { + ViewData["Title"] = "ViewComponent With ViewStart"; + return View(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Controllers/PartialsWithLayoutController.cs b/test/WebSites/RazorWebSite/Controllers/PartialsWithLayoutController.cs new file mode 100644 index 0000000000..c918a7482c --- /dev/null +++ b/test/WebSites/RazorWebSite/Controllers/PartialsWithLayoutController.cs @@ -0,0 +1,31 @@ +// 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 RazorWebSite.Controllers +{ + public class PartialsWithLayoutController : Controller + { + public IActionResult PartialDoesNotExecuteViewStarts() + { + return PartialView("PartialThatDoesNotSpecifyLayout"); + } + + // This action demonstrates + // (a) _ViewStart does not get executed when executing a partial via RenderPartial + // (b) Partials rendered via RenderPartial can execute Layout. + public IActionResult PartialsRenderedViaRenderPartial() + { + return View(); + } + + // This action demonstrates + // (a) _ViewStart does not get executed when executing a partial via PartialAsync + // (b) Partials rendered via PartialAsync can execute Layout. + public IActionResult PartialsRenderedViaPartialAsync() + { + return View(); + } + } +} diff --git a/test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs b/test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs index c625844f3a..b18dce3e40 100644 --- a/test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs +++ b/test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs @@ -60,5 +60,16 @@ namespace RazorWebSite.Controllers ViewData["data-from-controller"] = "hello from controller"; return View("ViewWithDataFromController"); } + + public ViewResult ViewWithComponentThatHasLayout() + { + ViewData["Title"] = "View With Component With Layout"; + return View(); + } + + public ViewResult ViewWithComponentThatHasViewStart() + { + return View(); + } } } \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/LayoutForViewStartWithLayout.cshtml b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/LayoutForViewStartWithLayout.cshtml new file mode 100644 index 0000000000..f84acc75ef --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/LayoutForViewStartWithLayout.cshtml @@ -0,0 +1 @@ +@RenderBody() \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatDoesNotSpecifyLayout.cshtml b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatDoesNotSpecifyLayout.cshtml new file mode 100644 index 0000000000..7160805707 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatDoesNotSpecifyLayout.cshtml @@ -0,0 +1 @@ +Partial that does not specify Layout \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatSpecifiesLayout.cshtml b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatSpecifiesLayout.cshtml new file mode 100644 index 0000000000..393fcc5be4 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatSpecifiesLayout.cshtml @@ -0,0 +1,4 @@ +@{ + Layout = "LayoutForViewStartWithLayout"; +} +Partial that specifies Layout diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaPartialAsync.cshtml b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaPartialAsync.cshtml new file mode 100644 index 0000000000..a31e73014b --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaPartialAsync.cshtml @@ -0,0 +1,2 @@ +@await Html.PartialAsync("PartialThatSpecifiesLayout") +@await Html.PartialAsync("PartialThatDoesNotSpecifyLayout") diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaRenderPartial.cshtml b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaRenderPartial.cshtml new file mode 100644 index 0000000000..a19efc0fce --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaRenderPartial.cshtml @@ -0,0 +1,4 @@ +@{ + await Html.RenderPartialAsync("PartialThatSpecifiesLayout"); + await Html.RenderPartialAsync("PartialThatDoesNotSpecifyLayout"); +} diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/_ViewStart.cshtml b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/_ViewStart.cshtml new file mode 100644 index 0000000000..4203835c70 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/PartialsWithLayout/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "LayoutForViewStartWithLayout"; +} \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithLayout/Default.cshtml b/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithLayout/Default.cshtml new file mode 100644 index 0000000000..77202448c3 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithLayout/Default.cshtml @@ -0,0 +1,4 @@ +@{ + Layout = "_ComponentLayout"; +} +Component With Layout \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/Default.cshtml b/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/Default.cshtml new file mode 100644 index 0000000000..e6b8bc36d4 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/Default.cshtml @@ -0,0 +1 @@ +@ViewBag.Title \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/_ViewStart.cshtml b/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/_ViewStart.cshtml new file mode 100644 index 0000000000..6da7c4c9a9 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + throw new Exception("This should not be invoked as part of executing the View Component."); +} \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/Shared/_ComponentLayout.cshtml b/test/WebSites/RazorWebSite/Views/Shared/_ComponentLayout.cshtml new file mode 100644 index 0000000000..1767ceda30 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/Shared/_ComponentLayout.cshtml @@ -0,0 +1,2 @@ +@ViewBag.Title +@RenderBody() \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasLayout.cshtml b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasLayout.cshtml new file mode 100644 index 0000000000..2008e37128 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasLayout.cshtml @@ -0,0 +1,5 @@ +@{ + Layout = "_LayoutWithTitle"; +} +Page Content +@await Component.InvokeAsync("ComponentWithLayout") diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasViewStart.cshtml b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasViewStart.cshtml new file mode 100644 index 0000000000..c0e538e707 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasViewStart.cshtml @@ -0,0 +1 @@ +@await Component.InvokeAsync("ComponentWithViewStart") From d51dad95604690f7a4059afa16398a666fa1bbcd Mon Sep 17 00:00:00 2001 From: Chris Ross Date: Tue, 20 Jan 2015 12:16:30 -0800 Subject: [PATCH 110/118] Handle IFileSystem rename. --- .../ActionResults/FilePathResult.cs | 30 ++++++----- src/Microsoft.AspNet.Mvc.Core/project.json | 2 +- .../Directives/ChunkInheritanceUtility.cs | 14 +++--- .../MvcRazorHost.cs | 16 +++--- .../project.json | 2 +- .../Compilation/CompilationResult.cs | 2 +- .../Compilation/CompilerCache.cs | 16 +++--- ...he.cs => DefaultRazorFileProviderCache.cs} | 22 ++++---- .../Compilation/ICompilationService.cs | 2 +- ...temCache.cs => IRazorFileProviderCache.cs} | 6 +-- .../Compilation/RoslynCompilationService.cs | 2 +- .../RazorViewEngineOptions.cs | 18 +++---- .../Razor/PreCompileViews/RazorPreCompiler.cs | 12 ++--- .../Razor/PreCompileViews/RelativeFileInfo.cs | 2 +- .../Razor/RazorFileHash.cs | 2 +- .../VirtualPathRazorPageFactory.cs | 14 +++--- src/Microsoft.AspNet.Mvc/MvcServices.cs | 6 +-- .../RazorViewEngineOptionsSetup.cs | 4 +- .../ActionResults/FilePathResultTest.cs | 50 +++++++++---------- .../RazorViewEngineOptionsTest.cs | 4 +- .../Directives/ChunkInheritanceUtilityTest.cs | 34 ++++++------- .../InjectChunkVisitorTest.cs | 2 +- .../ModelChunkVisitorTest.cs | 2 +- .../MvcRazorHostTest.cs | 18 +++---- .../TestFileInfo.cs | 2 +- ...{TestFileSystem.cs => TestFileProvider.cs} | 4 +- .../ViewStartUtilityTest.cs | 10 ++-- .../Compilation/CompilationResultTest.cs | 2 +- .../Compilation/CompilerCacheTest.cs | 44 ++++++++-------- ...s => DefaultRazorFileProviderCacheTest.cs} | 28 +++++------ .../RazorCompilationServiceTest.cs | 2 +- .../RazorViewEngineOptionsTest.cs | 4 +- .../project.json | 2 +- .../RazorViewEngineOptionsSetupTest.cs | 8 +-- .../RazorViewEngineOptionsWebsite/Startup.cs | 4 +- .../wwwroot/readme.md | 2 +- 36 files changed, 196 insertions(+), 198 deletions(-) rename src/Microsoft.AspNet.Mvc.Razor/Compilation/{DefaultRazorFileSystemCache.cs => DefaultRazorFileProviderCache.cs} (76%) rename src/Microsoft.AspNet.Mvc.Razor/Compilation/{IRazorFileSystemCache.cs => IRazorFileProviderCache.cs} (63%) rename test/Microsoft.AspNet.Mvc.Razor.Host.Test/{TestFileSystem.cs => TestFileProvider.cs} (94%) rename test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/{DefaultRazorFileSystemCacheTest.cs => DefaultRazorFileProviderCacheTest.cs} (93%) diff --git a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs index 4a5e1baabb..4bbf14a121 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ActionResults/FilePathResult.cs @@ -5,7 +5,7 @@ using System; using System.IO; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Interfaces; @@ -73,18 +73,18 @@ namespace Microsoft.AspNet.Mvc } /// - /// Gets or sets the used to resolve paths. + /// Gets or sets the used to resolve paths. /// - public IFileSystem FileSystem { get; set; } + public IFileProvider FileProvider { get; set; } /// protected override Task WriteFileAsync(HttpResponse response, CancellationToken cancellation) { var sendFile = response.HttpContext.GetFeature(); - var fileSystem = GetFileSystem(response.HttpContext.RequestServices); + var fileProvider = GetFileProvider(response.HttpContext.RequestServices); - var filePath = ResolveFilePath(fileSystem); + var filePath = ResolveFilePath(fileProvider); if (sendFile != null) { @@ -100,7 +100,7 @@ namespace Microsoft.AspNet.Mvc } } - internal string ResolveFilePath(IFileSystem fileSystem) + internal string ResolveFilePath(IFileProvider fileProvider) { // Let the file system try to get the file and if it can't, // fallback to trying the path directly unless the path starts with '/'. @@ -117,10 +117,10 @@ namespace Microsoft.AspNet.Mvc return path; } - var fileInfo = fileSystem.GetFileInfo(path); + var fileInfo = fileProvider.GetFileInfo(path); if (fileInfo.Exists) { - // The path is relative and IFileSystem found the file, so return the full + // The path is relative and IFileProvider found the file, so return the full // path. return fileInfo.PhysicalPath; } @@ -157,7 +157,7 @@ namespace Microsoft.AspNet.Mvc if (path.StartsWith("~\\", StringComparison.Ordinal)) { // ~\ is not a valid virtual path, and we don't want to replace '\' with '/' as it - // ofuscates the error, so just return the original path and throw at a later point + // obfuscates the error, so just return the original path and throw at a later point // when we can't find the file. return path; } @@ -195,19 +195,17 @@ namespace Microsoft.AspNet.Mvc path.StartsWith("\\\\", StringComparison.Ordinal); } - private IFileSystem GetFileSystem(IServiceProvider requestServices) + private IFileProvider GetFileProvider(IServiceProvider requestServices) { - if (FileSystem != null) + if (FileProvider != null) { - return FileSystem; + return FileProvider; } - // For right now until we can use IWebRootFileSystemProvider, see - // https://github.com/aspnet/Hosting/issues/86 for details. var hostingEnvironment = requestServices.GetService(); - FileSystem = new PhysicalFileSystem(hostingEnvironment.WebRoot); + FileProvider = hostingEnvironment.WebRootFileProvider; - return FileSystem; + return FileProvider; } private static async Task CopyStreamToResponse( diff --git a/src/Microsoft.AspNet.Mvc.Core/project.json b/src/Microsoft.AspNet.Mvc.Core/project.json index b63ef2f66c..4f5944b3cc 100644 --- a/src/Microsoft.AspNet.Mvc.Core/project.json +++ b/src/Microsoft.AspNet.Mvc.Core/project.json @@ -5,7 +5,7 @@ "warningsAsErrors": true }, "dependencies": { - "Microsoft.AspNet.FileSystems": "1.0.0-*", + "Microsoft.AspNet.FileProviders": "1.0.0-*", "Microsoft.AspNet.Hosting": "1.0.0-*", "Microsoft.AspNet.Http.Extensions": "1.0.0-*", "Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" }, diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkInheritanceUtility.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkInheritanceUtility.cs index f2ac93f7e9..3cd5b7bd6b 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkInheritanceUtility.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/Directives/ChunkInheritanceUtility.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.IO; using System.Linq; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Parser; @@ -20,21 +20,21 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives { private readonly Dictionary _parsedCodeTrees; private readonly MvcRazorHost _razorHost; - private readonly IFileSystem _fileSystem; + private readonly IFileProvider _fileProvider; private readonly IReadOnlyList _defaultInheritedChunks; /// /// Initializes a new instance of . /// /// The used to parse _ViewStart pages. - /// The filesystem that represents the application. + /// The fileProvider that represents the application. /// Sequence of s inherited by default. public ChunkInheritanceUtility([NotNull] MvcRazorHost razorHost, - [NotNull] IFileSystem fileSystem, + [NotNull] IFileProvider fileProvider, [NotNull] IReadOnlyList defaultInheritedChunks) { _razorHost = razorHost; - _fileSystem = fileSystem; + _fileProvider = fileProvider; _defaultInheritedChunks = defaultInheritedChunks; _parsedCodeTrees = new Dictionary(StringComparer.Ordinal); } @@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives /// /// Gets an ordered of parsed for each _ViewStart that /// is applicable to the page located at . The list is ordered so that the - /// for the _ViewStart closest to the in the filesystem + /// for the _ViewStart closest to the in the fileProvider /// appears first. /// /// The path of the page to locate inherited chunks for. @@ -62,7 +62,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives } else { - var fileInfo = _fileSystem.GetFileInfo(viewStartPath); + var fileInfo = _fileProvider.GetFileInfo(viewStartPath); if (fileInfo.Exists) { // viewStartPath contains the app-relative path of the ViewStart. diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs index be3b1a43d0..1dc32bdad4 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorHost.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Mvc.Razor.Directives; using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor.Generator; @@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.Razor new InjectChunk("Microsoft.AspNet.Mvc.IUrlHelper", "Url"), }; - private readonly IFileSystem _fileSystem; + private readonly IFileProvider _fileProvider; // CodeGenerationContext.DefaultBaseClass is set to MyBaseType. // This field holds the type name without the generic decoration (MyBaseType) @@ -45,18 +45,18 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// The path to the application base. public MvcRazorHost(string root) : - this(new PhysicalFileSystem(root)) + this(new PhysicalFileProvider(root)) { } #endif /// - /// Initializes a new instance of using the specified . + /// Initializes a new instance of using the specified . /// - /// A rooted at the application base path. - public MvcRazorHost(IFileSystem fileSystem) + /// A rooted at the application base path. + public MvcRazorHost(IFileProvider fileProvider) : base(new CSharpRazorCodeLanguage()) { - _fileSystem = fileSystem; + _fileProvider = fileProvider; _baseType = BaseType; TagHelperDescriptorResolver = new TagHelperDescriptorResolver(); @@ -164,7 +164,7 @@ namespace Microsoft.AspNet.Mvc.Razor if (_chunkInheritanceUtility == null) { // This needs to be lazily evaluated to support DefaultInheritedChunks being virtual. - _chunkInheritanceUtility = new ChunkInheritanceUtility(this, _fileSystem, DefaultInheritedChunks); + _chunkInheritanceUtility = new ChunkInheritanceUtility(this, _fileProvider, DefaultInheritedChunks); } return _chunkInheritanceUtility; diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/project.json b/src/Microsoft.AspNet.Mvc.Razor.Host/project.json index 0b7ef3cc78..43986a8885 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/project.json +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/project.json @@ -5,7 +5,7 @@ "warningsAsErrors": true }, "dependencies": { - "Microsoft.AspNet.FileSystems": "1.0.0-*", + "Microsoft.AspNet.FileProviders": "1.0.0-*", "Microsoft.AspNet.Mvc.Common": { "version": "6.0.0-*", "type": "build" }, "Microsoft.AspNet.Razor.Runtime": "4.0.0-*" }, diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs index 85dab4a29b..08b6f00b9e 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilationResult.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.IO; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; namespace Microsoft.AspNet.Mvc.Razor { diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs index c4a269d1bc..0035ec8a48 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/CompilerCache.cs @@ -6,7 +6,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Reflection; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; namespace Microsoft.AspNet.Mvc.Razor { @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.Razor public class CompilerCache : ICompilerCache { private readonly ConcurrentDictionary _cache; - private readonly IFileSystem _fileSystem; + private readonly IFileProvider _fileProvider; /// /// Initializes a new instance of populated with precompiled views @@ -24,18 +24,18 @@ namespace Microsoft.AspNet.Mvc.Razor /// An representing the assemblies /// used to search for pre-compiled views. /// - /// An instance that represents the application's + /// An instance that represents the application's /// file system. /// - public CompilerCache(IAssemblyProvider provider, IRazorFileSystemCache fileSystem) - : this(GetFileInfos(provider.CandidateAssemblies), fileSystem) + public CompilerCache(IAssemblyProvider provider, IRazorFileProviderCache fileProvider) + : this(GetFileInfos(provider.CandidateAssemblies), fileProvider) { } // Internal for unit testing - internal CompilerCache(IEnumerable viewCollections, IFileSystem fileSystem) + internal CompilerCache(IEnumerable viewCollections, IFileProvider fileProvider) { - _fileSystem = fileSystem; + _fileProvider = fileProvider; _cache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); foreach (var viewCollection in viewCollections) @@ -184,7 +184,7 @@ namespace Microsoft.AspNet.Mvc.Razor var viewStartLocations = ViewStartUtility.GetViewStartLocations(relativePath); foreach (var viewStartLocation in viewStartLocations) { - var viewStartFileInfo = _fileSystem.GetFileInfo(viewStartLocation); + var viewStartFileInfo = _fileProvider.GetFileInfo(viewStartLocation); if (viewStartFileInfo.Exists) { var relativeFileInfo = new RelativeFileInfo(viewStartFileInfo, viewStartLocation); diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileSystemCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileProviderCache.cs similarity index 76% rename from src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileSystemCache.cs rename to src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileProviderCache.cs index d03d66def9..3801f4fec7 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileSystemCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/DefaultRazorFileProviderCache.cs @@ -3,31 +3,31 @@ using System; using System.Collections.Concurrent; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.Framework.Expiration.Interfaces; using Microsoft.Framework.OptionsModel; namespace Microsoft.AspNet.Mvc.Razor { /// - /// Default implementation for the interface that caches - /// the results of . + /// Default implementation for the interface that caches + /// the results of . /// - public class DefaultRazorFileSystemCache : IRazorFileSystemCache + public class DefaultRazorFileProviderCache : IRazorFileProviderCache { private readonly ConcurrentDictionary _fileInfoCache = new ConcurrentDictionary(StringComparer.Ordinal); - private readonly IFileSystem _fileSystem; + private readonly IFileProvider _fileProvider; private readonly TimeSpan _offset; /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// /// Accessor to . - public DefaultRazorFileSystemCache(IOptions optionsAccessor) + public DefaultRazorFileProviderCache(IOptions optionsAccessor) { - _fileSystem = optionsAccessor.Options.FileSystem; + _fileProvider = optionsAccessor.Options.FileProvider; _offset = optionsAccessor.Options.ExpirationBeforeCheckingFilesOnDisk; } @@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// public IDirectoryContents GetDirectoryContents(string subpath) { - return _fileSystem.GetDirectoryContents(subpath); + return _fileProvider.GetDirectoryContents(subpath); } /// @@ -58,7 +58,7 @@ namespace Microsoft.AspNet.Mvc.Razor } else { - var fileInfo = _fileSystem.GetFileInfo(subpath); + var fileInfo = _fileProvider.GetFileInfo(subpath); expiringFileInfo = new ExpiringFileInfo() { @@ -75,7 +75,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// public IExpirationTrigger Watch(string filter) { - return _fileSystem.Watch(filter); + return _fileProvider.Watch(filter); } private class ExpiringFileInfo diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs index 730ca831a5..24ea983e3d 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/ICompilationService.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; namespace Microsoft.AspNet.Mvc.Razor { diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/IRazorFileSystemCache.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/IRazorFileProviderCache.cs similarity index 63% rename from src/Microsoft.AspNet.Mvc.Razor/Compilation/IRazorFileSystemCache.cs rename to src/Microsoft.AspNet.Mvc.Razor/Compilation/IRazorFileProviderCache.cs index 333dfe668c..718f7cf567 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/IRazorFileSystemCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/IRazorFileProviderCache.cs @@ -1,15 +1,15 @@ // 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.FileSystems; +using Microsoft.AspNet.FileProviders; namespace Microsoft.AspNet.Mvc.Razor { /// - /// An that caches the results of for a + /// An that caches the results of for a /// duration specified by . /// - public interface IRazorFileSystemCache : IFileSystem + public interface IRazorFileProviderCache : IFileProvider { } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs index 943ac3fcda..9dd1acb69b 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Compilation/RoslynCompilationService.cs @@ -10,7 +10,7 @@ using System.Linq; using System.Reflection; using System.Reflection.PortableExecutable; using System.Runtime.InteropServices; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Emit; diff --git a/src/Microsoft.AspNet.Mvc.Razor/OptionDescriptors/RazorViewEngineOptions.cs b/src/Microsoft.AspNet.Mvc.Razor/OptionDescriptors/RazorViewEngineOptions.cs index 828390e275..f49f45ecb4 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/OptionDescriptors/RazorViewEngineOptions.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/OptionDescriptors/RazorViewEngineOptions.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Mvc.Razor.OptionDescriptors; namespace Microsoft.AspNet.Mvc.Razor @@ -14,12 +14,12 @@ namespace Microsoft.AspNet.Mvc.Razor public class RazorViewEngineOptions { private TimeSpan _expirationBeforeCheckingFilesOnDisk = TimeSpan.FromSeconds(2); - private IFileSystem _fileSystem; + private IFileProvider _fileProvider; /// /// Gets or sets the that specifies the duration for which results of - /// are cached by . - /// is used to query for file changes during Razor compilation. + /// are cached by . + /// is used to query for file changes during Razor compilation. /// /// /// of or less, means no caching. @@ -53,16 +53,16 @@ namespace Microsoft.AspNet.Mvc.Razor = new List(); /// - /// Gets or sets the used by to locate Razor files on + /// Gets or sets the used by to locate Razor files on /// disk. /// /// - /// At startup, this is initialized to an instance of that is rooted at the + /// At startup, this is initialized to an instance of that is rooted at the /// application root. /// - public IFileSystem FileSystem + public IFileProvider FileProvider { - get { return _fileSystem; } + get { return _fileProvider; } set { @@ -71,7 +71,7 @@ namespace Microsoft.AspNet.Mvc.Razor throw new ArgumentNullException(nameof(value)); } - _fileSystem = value; + _fileProvider = value; } } } diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs index 4bbe5b90a5..df11e09345 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.CodeAnalysis; using Microsoft.Framework.Cache.Memory; using Microsoft.Framework.DependencyInjection; @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.Razor public class RazorPreCompiler { private readonly IServiceProvider _serviceProvider; - private readonly IFileSystem _fileSystem; + private readonly IFileProvider _fileProvider; public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider, [NotNull] IMemoryCache precompilationCache, @@ -37,7 +37,7 @@ namespace Microsoft.AspNet.Mvc.Razor [NotNull] CompilationSettings compilationSettings) { _serviceProvider = designTimeServiceProvider; - _fileSystem = optionsAccessor.Options.FileSystem; + _fileProvider = optionsAccessor.Options.FileProvider; CompilationSettings = compilationSettings; PreCompilationCache = precompilationCache; } @@ -120,10 +120,10 @@ namespace Microsoft.AspNet.Mvc.Razor if (entry != null) { - cacheSetContext.AddExpirationTrigger(_fileSystem.Watch(fileInfo.RelativePath)); + cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(fileInfo.RelativePath)); foreach (var viewStartPath in ViewStartUtility.GetViewStartLocations(fileInfo.RelativePath)) { - cacheSetContext.AddExpirationTrigger(_fileSystem.Watch(viewStartPath)); + cacheSetContext.AddExpirationTrigger(_fileProvider.Watch(viewStartPath)); } } @@ -132,7 +132,7 @@ namespace Microsoft.AspNet.Mvc.Razor private void GetFileInfosRecursive(string root, List razorFiles) { - var fileInfos = _fileSystem.GetDirectoryContents(root); + var fileInfos = _fileProvider.GetDirectoryContents(root); foreach (var fileInfo in fileInfos) { diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RelativeFileInfo.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RelativeFileInfo.cs index d0100620da..b0bb2b2b4a 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RelativeFileInfo.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RelativeFileInfo.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; namespace Microsoft.AspNet.Mvc.Razor { diff --git a/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorFileHash.cs b/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorFileHash.cs index bb752cbb97..9f2176414d 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorFileHash.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/Razor/RazorFileHash.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Security.Cryptography; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; namespace Microsoft.AspNet.Mvc.Razor { diff --git a/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs b/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs index befa9540ed..a2ba401b34 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/VirtualPathRazorPageFactory.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Http; using Microsoft.AspNet.PageExecutionInstrumentation; using Microsoft.Framework.DependencyInjection; @@ -17,19 +17,19 @@ namespace Microsoft.AspNet.Mvc.Razor { private readonly ITypeActivator _activator; private readonly IServiceProvider _serviceProvider; - private readonly IRazorFileSystemCache _fileSystemCache; + private readonly IRazorFileProviderCache _fileProviderCache; private readonly ICompilerCache _compilerCache; private IRazorCompilationService _razorcompilationService; public VirtualPathRazorPageFactory(ITypeActivator typeActivator, IServiceProvider serviceProvider, ICompilerCache compilerCache, - IRazorFileSystemCache fileSystemCache) + IRazorFileProviderCache fileProviderCache) { _activator = typeActivator; _serviceProvider = serviceProvider; _compilerCache = compilerCache; - _fileSystemCache = fileSystemCache; + _fileProviderCache = fileProviderCache; } private IRazorCompilationService RazorCompilationService @@ -40,7 +40,7 @@ namespace Microsoft.AspNet.Mvc.Razor { // it is ok to use the cached service provider because both this, and the // resolved service are in a lifetime of Scoped. - // We don't want to get it upgront because it will force Roslyn to load. + // We don't want to get it upfront because it will force Roslyn to load. _razorcompilationService = _serviceProvider.GetRequiredService(); } @@ -53,11 +53,11 @@ namespace Microsoft.AspNet.Mvc.Razor { if (relativePath.StartsWith("~/", StringComparison.Ordinal)) { - // For tilde slash paths, drop the leading ~ to make it work with the underlying IFileSystem. + // For tilde slash paths, drop the leading ~ to make it work with the underlying IFileProvider. relativePath = relativePath.Substring(1); } - var fileInfo = _fileSystemCache.GetFileInfo(relativePath); + var fileInfo = _fileProviderCache.GetFileInfo(relativePath); if (fileInfo.Exists) { diff --git a/src/Microsoft.AspNet.Mvc/MvcServices.cs b/src/Microsoft.AspNet.Mvc/MvcServices.cs index 1e2b5aa84f..745481bd5b 100644 --- a/src/Microsoft.AspNet.Mvc/MvcServices.cs +++ b/src/Microsoft.AspNet.Mvc/MvcServices.cs @@ -102,13 +102,13 @@ namespace Microsoft.AspNet.Mvc yield return describe.Transient(); // Caches view locations that are valid for the lifetime of the application. yield return describe.Singleton(); - yield return describe.Singleton(); + yield return describe.Singleton(); // The host is designed to be discarded after consumption and is very inexpensive to initialize. yield return describe.Transient(serviceProvider => { - var cachedFileSystem = serviceProvider.GetRequiredService(); - return new MvcRazorHost(cachedFileSystem); + var cachedFileProvider = serviceProvider.GetRequiredService(); + return new MvcRazorHost(cachedFileProvider); }); // Caches compilation artifacts across the lifetime of the application. diff --git a/src/Microsoft.AspNet.Mvc/RazorViewEngineOptionsSetup.cs b/src/Microsoft.AspNet.Mvc/RazorViewEngineOptionsSetup.cs index 281d458406..16b931392e 100644 --- a/src/Microsoft.AspNet.Mvc/RazorViewEngineOptionsSetup.cs +++ b/src/Microsoft.AspNet.Mvc/RazorViewEngineOptionsSetup.cs @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Mvc.Razor; using Microsoft.Framework.OptionsModel; using Microsoft.Framework.Runtime; @@ -26,7 +26,7 @@ namespace Microsoft.AspNet.Mvc private static void ConfigureRazor(RazorViewEngineOptions razorOptions, IApplicationEnvironment applicationEnvironment) { - razorOptions.FileSystem = new PhysicalFileSystem(applicationEnvironment.ApplicationBasePath); + razorOptions.FileProvider = new PhysicalFileProvider(applicationEnvironment.ApplicationBasePath); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs index b84bedfe56..995d8c4a1d 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/FilePathResultTest.cs @@ -4,7 +4,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Hosting; using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Http.Interfaces; @@ -37,7 +37,7 @@ namespace Microsoft.AspNet.Mvc var result = new FilePathResult(path, "text/plain") { - FileSystem = new PhysicalFileSystem(Path.GetFullPath(".")), + FileProvider = new PhysicalFileProvider(Path.GetFullPath(".")), }; var httpContext = new DefaultHttpContext(); @@ -56,15 +56,15 @@ namespace Microsoft.AspNet.Mvc } [Fact] - public async Task ExecuteResultAsync_FallsBackToThePhysicalFileSystem_IfNoFileSystemIsPresent() + public async Task ExecuteResultAsync_FallsBackToThePhysicalFileProvider_IfNoFileProviderIsPresent() { // Arrange var path = Path.Combine("TestFiles", "FilePathResultTestFile.txt"); var result = new FilePathResult(path, "text/plain"); var appEnvironment = new Mock(); - appEnvironment.Setup(app => app.WebRoot) - .Returns(Directory.GetCurrentDirectory()); + appEnvironment.Setup(app => app.WebRootFileProvider) + .Returns(new PhysicalFileProvider(Directory.GetCurrentDirectory())); var httpContext = new DefaultHttpContext(); httpContext.Response.Body = new MemoryStream(); @@ -92,7 +92,7 @@ namespace Microsoft.AspNet.Mvc var result = new FilePathResult(path, "text/plain") { - FileSystem = new PhysicalFileSystem(Path.GetFullPath(".")), + FileProvider = new PhysicalFileProvider(Path.GetFullPath(".")), }; var sendFileMock = new Mock(); @@ -122,10 +122,10 @@ namespace Microsoft.AspNet.Mvc // forward slashes. path = path.Replace('/', '\\'); - // Point the FileSystemRoot to a subfolder + // Point the FileProviderRoot to a subfolder var result = new FilePathResult(path, "text/plain") { - FileSystem = new PhysicalFileSystem(Path.GetFullPath("Utils")), + FileProvider = new PhysicalFileProvider(Path.GetFullPath("Utils")), }; var httpContext = new DefaultHttpContext(); @@ -151,10 +151,10 @@ namespace Microsoft.AspNet.Mvc var path = Path.GetFullPath(Path.Combine(".", "TestFiles", "FilePathResultTestFile.txt")); path = path.Replace(@"\", "/"); - // Point the FileSystemRoot to a subfolder + // Point the FileProviderRoot to a subfolder var result = new FilePathResult(path, "text/plain") { - FileSystem = new PhysicalFileSystem(Path.GetFullPath("Utils")), + FileProvider = new PhysicalFileProvider(Path.GetFullPath("Utils")), }; var httpContext = new DefaultHttpContext(); @@ -203,15 +203,15 @@ namespace Microsoft.AspNet.Mvc public void GetFilePath_Resolves_RelativePaths(string path, string relativePathToFile) { // Arrange - var fileSystem = new PhysicalFileSystem(Path.GetFullPath("./TestFiles")); + var fileProvider = new PhysicalFileProvider(Path.GetFullPath("./TestFiles")); var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePathToFile)); var filePathResult = new FilePathResult(path, "text/plain") { - FileSystem = fileSystem, + FileProvider = fileProvider, }; // Act - var result = filePathResult.ResolveFilePath(fileSystem); + var result = filePathResult.ResolveFilePath(fileProvider); // Assert Assert.Equal(expectedPath, result); @@ -224,15 +224,15 @@ namespace Microsoft.AspNet.Mvc public void GetFilePath_FailsToResolve_InvalidVirtualPaths(string path, string relativePathToFile) { // Arrange - var fileSystem = new PhysicalFileSystem(Path.GetFullPath("./TestFiles")); + var fileProvider = new PhysicalFileProvider(Path.GetFullPath("./TestFiles")); var expectedPath = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), relativePathToFile)); var filePathResult = new FilePathResult(path, "text/plain") { - FileSystem = fileSystem, + FileProvider = fileProvider, }; // Act - var ex = Assert.Throws(() => filePathResult.ResolveFilePath(fileSystem)); + var ex = Assert.Throws(() => filePathResult.ResolveFilePath(fileProvider)); // Assert Assert.Equal("Could not find file: " + path, ex.Message); @@ -271,18 +271,18 @@ namespace Microsoft.AspNet.Mvc { // Arrange - // Point the IFileSystem root to a different subfolder - var fileSystem = new PhysicalFileSystem(Path.GetFullPath("./Utils")); + // Point the IFileProvider root to a different subfolder + var fileProvider = new PhysicalFileProvider(Path.GetFullPath("./Utils")); var filePathResult = new FilePathResult(path, "text/plain") { - FileSystem = fileSystem, + FileProvider = fileProvider, }; var expectedFileName = path.TrimStart('~').Replace('\\', '/'); var expectedMessage = "Could not find file: " + expectedFileName; // Act - var ex = Assert.Throws(() => filePathResult.ResolveFilePath(fileSystem)); + var ex = Assert.Throws(() => filePathResult.ResolveFilePath(fileProvider)); // Assert Assert.Equal(expectedMessage, ex.Message); @@ -322,7 +322,7 @@ namespace Microsoft.AspNet.Mvc // Arrange var fileResult = new FilePathResult(path, "text/plain") { - FileSystem = Mock.Of(), + FileProvider = Mock.Of(), }; // Act @@ -342,7 +342,7 @@ namespace Microsoft.AspNet.Mvc // Arrange var fileResult = new FilePathResult(path, "text/plain") { - FileSystem = Mock.Of(), + FileProvider = Mock.Of(), }; // Act @@ -362,7 +362,7 @@ namespace Microsoft.AspNet.Mvc // Arrange var fileResult = new FilePathResult(path, "text/plain") { - FileSystem = Mock.Of(), + FileProvider = Mock.Of(), }; // Act @@ -382,7 +382,7 @@ namespace Microsoft.AspNet.Mvc // Arrange var fileResult = new FilePathResult(path, "text/plain") { - FileSystem = Mock.Of(), + FileProvider = Mock.Of(), }; // Act @@ -427,7 +427,7 @@ namespace Microsoft.AspNet.Mvc // Arrange var fileResult = new FilePathResult(path, "text/plain") { - FileSystem = Mock.Of(), + FileProvider = Mock.Of(), }; // Act diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewEngineOptionsTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewEngineOptionsTest.cs index 8358e3ce2a..e792016413 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewEngineOptionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewEngineOptionsTest.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests private readonly Action _app = new Startup().Configure; [Fact] - public async Task RazorViewEngine_UsesFileSystemOnViewEngineOptionsToLocateViews() + public async Task RazorViewEngine_UsesFileProviderOnViewEngineOptionsToLocateViews() { // Arrange var expectedMessage = "Hello test-user, this is /RazorViewEngineOptions_Home"; @@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests } [Fact] - public async Task RazorViewEngine_UsesFileSystemOnViewEngineOptionsToLocateAreaViews() + public async Task RazorViewEngine_UsesFileProviderOnViewEngineOptionsToLocateAreaViews() { // Arrange var expectedMessage = "Hello admin-user, this is /Restricted/RazorViewEngineOptions_Admin/Login"; diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/ChunkInheritanceUtilityTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/ChunkInheritanceUtilityTest.cs index 48ccaa0e84..565918861c 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/ChunkInheritanceUtilityTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/Directives/ChunkInheritanceUtilityTest.cs @@ -12,11 +12,11 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives public void GetInheritedChunks_ReadsChunksFromViewStartsInPath() { // Arrange - var fileSystem = new TestFileSystem(); - fileSystem.AddFile(@"Views\accounts\_ViewStart.cshtml", "@using AccountModels"); - fileSystem.AddFile(@"Views\Shared\_ViewStart.cshtml", "@inject SharedHelper Shared"); - fileSystem.AddFile(@"Views\home\_ViewStart.cshtml", "@using MyNamespace"); - fileSystem.AddFile(@"Views\_ViewStart.cshtml", + var fileProvider = new TestFileProvider(); + fileProvider.AddFile(@"Views\accounts\_ViewStart.cshtml", "@using AccountModels"); + fileProvider.AddFile(@"Views\Shared\_ViewStart.cshtml", "@inject SharedHelper Shared"); + fileProvider.AddFile(@"Views\home\_ViewStart.cshtml", "@using MyNamespace"); + fileProvider.AddFile(@"Views\_ViewStart.cshtml", @"@inject MyHelper Helper @inherits MyBaseType @@ -30,8 +30,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives new InjectChunk("MyTestHtmlHelper", "Html"), new UsingChunk { Namespace = "AppNamespace.Model" }, }; - var host = new MvcRazorHost(fileSystem); - var utility = new ChunkInheritanceUtility(host, fileSystem, defaultChunks); + var host = new MvcRazorHost(fileProvider); + var utility = new ChunkInheritanceUtility(host, fileProvider, defaultChunks); // Act var codeTrees = utility.GetInheritedCodeTrees(@"Views\home\Index.cshtml"); @@ -66,17 +66,17 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives public void GetInheritedChunks_ReturnsEmptySequenceIfNoViewStartsArePresent() { // Arrange - var fileSystem = new TestFileSystem(); - fileSystem.AddFile(@"_ViewStart.cs", string.Empty); - fileSystem.AddFile(@"Views\_Layout.cshtml", string.Empty); - fileSystem.AddFile(@"Views\home\_not-viewstart.cshtml", string.Empty); - var host = new MvcRazorHost(fileSystem); + var fileProvider = new TestFileProvider(); + fileProvider.AddFile(@"_ViewStart.cs", string.Empty); + fileProvider.AddFile(@"Views\_Layout.cshtml", string.Empty); + fileProvider.AddFile(@"Views\home\_not-viewstart.cshtml", string.Empty); + var host = new MvcRazorHost(fileProvider); var defaultChunks = new Chunk[] { new InjectChunk("MyTestHtmlHelper", "Html"), new UsingChunk { Namespace = "AppNamespace.Model" }, }; - var utility = new ChunkInheritanceUtility(host, fileSystem, defaultChunks); + var utility = new ChunkInheritanceUtility(host, fileProvider, defaultChunks); // Act var codeTrees = utility.GetInheritedCodeTrees(@"Views\home\Index.cshtml"); @@ -89,10 +89,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives public void MergeInheritedChunks_MergesDefaultInheritedChunks() { // Arrange - var fileSystem = new TestFileSystem(); - fileSystem.AddFile(@"Views\_ViewStart.cshtml", + var fileProvider = new TestFileProvider(); + fileProvider.AddFile(@"Views\_ViewStart.cshtml", "@inject DifferentHelper Html"); - var host = new MvcRazorHost(fileSystem); + var host = new MvcRazorHost(fileProvider); var defaultChunks = new Chunk[] { new InjectChunk("MyTestHtmlHelper", "Html"), @@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Directives } }; - var utility = new ChunkInheritanceUtility(host, fileSystem, defaultChunks); + var utility = new ChunkInheritanceUtility(host, fileProvider, defaultChunks); var codeTree = new CodeTree(); // Act diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs index fdc73a475b..61031fa017 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/InjectChunkVisitorTest.cs @@ -146,7 +146,7 @@ MyType1 private static CodeBuilderContext CreateContext() { return new CodeBuilderContext( - new CodeGeneratorContext(new MvcRazorHost(new TestFileSystem()), + new CodeGeneratorContext(new MvcRazorHost(new TestFileProvider()), "MyClass", "MyNamespace", string.Empty, diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs index f19d22ce36..d26bbc74b6 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ModelChunkVisitorTest.cs @@ -102,7 +102,7 @@ Environment.NewLine + private static CodeBuilderContext CreateContext() { return new CodeBuilderContext( - new CodeGeneratorContext(new MvcRazorHost(new TestFileSystem()), + new CodeGeneratorContext(new MvcRazorHost(new TestFileProvider()), "MyClass", "MyNamespace", string.Empty, diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs index 9b17b11561..792c50083e 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.IO; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor.Generator; using Microsoft.AspNet.Razor.Generator.Compiler; @@ -19,7 +19,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void MvcRazorHost_EnablesInstrumentationByDefault() { // Arrange - var host = new MvcRazorHost(new TestFileSystem()); + var host = new MvcRazorHost(new TestFileProvider()); // Act var instrumented = host.EnableInstrumentation; @@ -32,7 +32,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void MvcRazorHost_GeneratesTagHelperModelExpressionCode_DesignTime() { // Arrange - var host = new MvcRazorHost(new TestFileSystem()) + var host = new MvcRazorHost(new TestFileProvider()) { DesignTimeMode = true }; @@ -84,7 +84,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void MvcRazorHost_ParsesAndGeneratesCodeForBasicScenarios(string scenarioName) { // Arrange - var host = new TestMvcRazorHost(new TestFileSystem()); + var host = new TestMvcRazorHost(new TestFileProvider()); // Act and Assert RunRuntimeTest(host, scenarioName); @@ -94,7 +94,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void InjectVisitor_GeneratesCorrectLineMappings() { // Arrange - var host = new MvcRazorHost(new TestFileSystem()) + var host = new MvcRazorHost(new TestFileProvider()) { DesignTimeMode = true }; @@ -113,7 +113,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void InjectVisitorWithModel_GeneratesCorrectLineMappings() { // Arrange - var host = new MvcRazorHost(new TestFileSystem()) + var host = new MvcRazorHost(new TestFileProvider()) { DesignTimeMode = true }; @@ -133,7 +133,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void ModelVisitor_GeneratesCorrectLineMappings() { // Arrange - var host = new MvcRazorHost(new TestFileSystem()) + var host = new MvcRazorHost(new TestFileProvider()) { DesignTimeMode = true }; @@ -229,8 +229,8 @@ namespace Microsoft.AspNet.Mvc.Razor /// private class TestMvcRazorHost : MvcRazorHost { - public TestMvcRazorHost(IFileSystem fileSystem) - : base(fileSystem) + public TestMvcRazorHost(IFileProvider fileProvider) + : base(fileProvider) { } public override CodeBuilder DecorateCodeBuilder(CodeBuilder incomingBuilder, CodeBuilderContext context) diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs index b561494fef..c56cb96ed6 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs @@ -4,7 +4,7 @@ using System; using System.IO; using System.Text; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; namespace Microsoft.AspNet.Mvc.Razor { diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs similarity index 94% rename from test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs rename to test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs index 84dfc25b80..319be33af5 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs @@ -4,12 +4,12 @@ using System; using System.Collections.Generic; using System.IO; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.Framework.Expiration.Interfaces; namespace Microsoft.AspNet.Mvc.Razor { - public class TestFileSystem : IFileSystem + public class TestFileProvider : IFileProvider { private readonly Dictionary _lookup = new Dictionary(StringComparer.Ordinal); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ViewStartUtilityTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ViewStartUtilityTest.cs index be00bf8065..ed589da934 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ViewStartUtilityTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/ViewStartUtilityTest.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.Framework.Runtime; using Microsoft.Framework.Runtime.Infrastructure; using Xunit; @@ -56,7 +56,7 @@ namespace Microsoft.AspNet.Mvc.Razor @"Views\_ViewStart.cshtml", @"_ViewStart.cshtml" }; - var fileSystem = new PhysicalFileSystem(GetTestFileSystemBase()); + var fileProvider = new PhysicalFileProvider(GetTestFileProviderBase()); // Act var result = ViewStartUtility.GetViewStartLocations(inputPath); @@ -116,7 +116,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void GetViewStartLocations_ReturnsEmptySequence_IfViewStartIsAtRoot() { // Arrange - var appBase = GetTestFileSystemBase(); + var appBase = GetTestFileProviderBase(); var viewPath = "_ViewStart.cshtml"; // Act @@ -130,7 +130,7 @@ namespace Microsoft.AspNet.Mvc.Razor public void GetViewStartLocations_ReturnsEmptySequence_IfPathIsRooted() { // Arrange - var appBase = GetTestFileSystemBase(); + var appBase = GetTestFileProviderBase(); var absolutePath = Path.Combine(Directory.GetCurrentDirectory(), "Index.cshtml"); // Act @@ -140,7 +140,7 @@ namespace Microsoft.AspNet.Mvc.Razor Assert.Empty(result); } - private static string GetTestFileSystemBase() + private static string GetTestFileProviderBase() { var serviceProvider = CallContextServiceLocator.Locator.ServiceProvider; var appEnv = (IApplicationEnvironment)serviceProvider.GetService(typeof(IApplicationEnvironment)); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs index 5334f46726..c3e36d28e2 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilationResultTest.cs @@ -3,7 +3,7 @@ using System.IO; using System.Text; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Moq; using Xunit; diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs index 5c7e22ddc8..1b3f501d9d 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/CompilerCacheTest.cs @@ -16,8 +16,8 @@ namespace Microsoft.AspNet.Mvc.Razor public void GetOrAdd_ReturnsCompilationResultFromFactory() { // Arrange - var fileSystem = new TestFileSystem(); - var cache = new CompilerCache(Enumerable.Empty(), fileSystem); + var fileProvider = new TestFileProvider(); + var cache = new CompilerCache(Enumerable.Empty(), fileProvider); var fileInfo = new TestFileInfo { LastModified = DateTime.FromFileTimeUtc(10000) @@ -111,8 +111,8 @@ namespace Microsoft.AspNet.Mvc.Razor var instance = new RuntimeCompileIdentical(); var length = Encoding.UTF8.GetByteCount(instance.Content); var collection = new ViewCollection(); - var fileSystem = new TestFileSystem(); - var cache = new CompilerCache(new[] { new ViewCollection() }, fileSystem); + var fileProvider = new TestFileProvider(); + var cache = new CompilerCache(new[] { new ViewCollection() }, fileProvider); var fileInfo = new TestFileInfo { @@ -153,8 +153,8 @@ namespace Microsoft.AspNet.Mvc.Razor var instance = (View)Activator.CreateInstance(resultViewType); var length = Encoding.UTF8.GetByteCount(instance.Content); var collection = new ViewCollection(); - var fileSystem = new TestFileSystem(); - var cache = new CompilerCache(new[] { new ViewCollection() }, fileSystem); + var fileProvider = new TestFileProvider(); + var cache = new CompilerCache(new[] { new ViewCollection() }, fileProvider); var fileInfo = new TestFileInfo { @@ -189,7 +189,7 @@ namespace Microsoft.AspNet.Mvc.Razor // Arrange var instance = (View)Activator.CreateInstance(typeof(PreCompile)); var length = Encoding.UTF8.GetByteCount(instance.Content); - var fileSystem = new TestFileSystem(); + var fileProvider = new TestFileProvider(); var lastModified = DateTime.UtcNow; @@ -207,7 +207,7 @@ namespace Microsoft.AspNet.Mvc.Razor Content = viewStartContent, LastModified = DateTime.UtcNow }; - fileSystem.AddFile("_ViewStart.cshtml", viewStartFileInfo); + fileProvider.AddFile("_ViewStart.cshtml", viewStartFileInfo); var viewStartRazorFileInfo = new RazorFileInfo { Hash = RazorFileHash.GetHash(GetMemoryStream(viewStartContent)), @@ -219,7 +219,7 @@ namespace Microsoft.AspNet.Mvc.Razor var precompiledViews = new ViewCollection(); precompiledViews.Add(viewStartRazorFileInfo); - var cache = new CompilerCache(new[] { precompiledViews }, fileSystem); + var cache = new CompilerCache(new[] { precompiledViews }, fileProvider); // Act var actual = cache.GetOrAdd(runtimeFileInfo, @@ -235,18 +235,18 @@ namespace Microsoft.AspNet.Mvc.Razor // Arrange var expectedType = typeof(RuntimeCompileDifferent); var lastModified = DateTime.UtcNow; - var fileSystem = new TestFileSystem(); + var fileProvider = new TestFileProvider(); var collection = new ViewCollection(); var precompiledFile = collection.FileInfos[0]; precompiledFile.RelativePath = "Views\\home\\index.cshtml"; - var cache = new CompilerCache(new[] { collection }, fileSystem); + var cache = new CompilerCache(new[] { collection }, fileProvider); var testFile = new TestFileInfo { Content = new PreCompile().Content, LastModified = precompiledFile.LastModified, PhysicalPath = precompiledFile.RelativePath }; - fileSystem.AddFile(precompiledFile.RelativePath, testFile); + fileProvider.AddFile(precompiledFile.RelativePath, testFile); var relativeFile = new RelativeFileInfo(testFile, testFile.PhysicalPath); // Act 1 @@ -257,7 +257,7 @@ namespace Microsoft.AspNet.Mvc.Razor Assert.Equal(typeof(PreCompile), actual1.CompiledType); // Act 2 - fileSystem.AddFile("Views\\_ViewStart.cshtml", ""); + fileProvider.AddFile("Views\\_ViewStart.cshtml", ""); var actual2 = cache.GetOrAdd(relativeFile, compile: _ => CompilationResult.Successful(expectedType)); @@ -271,7 +271,7 @@ namespace Microsoft.AspNet.Mvc.Razor // Arrange var expectedType = typeof(RuntimeCompileDifferent); var lastModified = DateTime.UtcNow; - var fileSystem = new TestFileSystem(); + var fileProvider = new TestFileProvider(); var viewCollection = new ViewCollection(); var precompiledView = viewCollection.FileInfos[0]; @@ -282,7 +282,7 @@ namespace Microsoft.AspNet.Mvc.Razor LastModified = precompiledView.LastModified, PhysicalPath = precompiledView.RelativePath }; - fileSystem.AddFile(viewFileInfo.PhysicalPath, viewFileInfo); + fileProvider.AddFile(viewFileInfo.PhysicalPath, viewFileInfo); var viewStartFileInfo = new TestFileInfo { @@ -298,10 +298,10 @@ namespace Microsoft.AspNet.Mvc.Razor Hash = RazorFileHash.GetHash(viewStartFileInfo), Length = viewStartFileInfo.Length }; - fileSystem.AddFile(viewStartFileInfo.PhysicalPath, viewStartFileInfo); + fileProvider.AddFile(viewStartFileInfo.PhysicalPath, viewStartFileInfo); viewCollection.Add(viewStart); - var cache = new CompilerCache(new[] { viewCollection }, fileSystem); + var cache = new CompilerCache(new[] { viewCollection }, fileProvider); var fileInfo = new RelativeFileInfo(viewFileInfo, viewFileInfo.PhysicalPath); // Act 1 @@ -312,7 +312,7 @@ namespace Microsoft.AspNet.Mvc.Razor Assert.Equal(typeof(PreCompile), actual1.CompiledType); // Act 2 - fileSystem.DeleteFile(viewStartFileInfo.PhysicalPath); + fileProvider.DeleteFile(viewStartFileInfo.PhysicalPath); var actual2 = cache.GetOrAdd(fileInfo, compile: _ => CompilationResult.Successful(expectedType)); @@ -387,10 +387,10 @@ namespace Microsoft.AspNet.Mvc.Razor RelativePath = fileInfo.PhysicalPath, }; - var fileSystem = new TestFileSystem(); - fileSystem.AddFile(viewStartRazorFileInfo.RelativePath, viewStartFileInfo); + var fileProvider = new TestFileProvider(); + fileProvider.AddFile(viewStartRazorFileInfo.RelativePath, viewStartFileInfo); var viewCollection = new ViewCollection(); - var cache = new CompilerCache(new[] { viewCollection }, fileSystem); + var cache = new CompilerCache(new[] { viewCollection }, fileProvider); // Act var actual = cache.GetOrAdd(runtimeFileInfo, @@ -405,7 +405,7 @@ namespace Microsoft.AspNet.Mvc.Razor { // Arrange var lastModified = DateTime.UtcNow; - var cache = new CompilerCache(Enumerable.Empty(), new TestFileSystem()); + var cache = new CompilerCache(Enumerable.Empty(), new TestFileProvider()); var fileInfo = new TestFileInfo { PhysicalPath = "test", diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileSystemCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileProviderCacheTest.cs similarity index 93% rename from test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileSystemCacheTest.cs rename to test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileProviderCacheTest.cs index 3ff4f5ed29..668fc76a50 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileSystemCacheTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/DefaultRazorFileProviderCacheTest.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.Framework.OptionsModel; using Microsoft.Framework.Expiration.Interfaces; using Moq; @@ -11,11 +11,11 @@ using Xunit; namespace Microsoft.AspNet.Mvc.Razor { - public class DefaultRazorFileSystemCacheTest + public class DefaultRazorFileProviderCacheTest { private const string FileName = "myView.cshtml"; - public DummyFileSystem TestFileSystem { get; } = new DummyFileSystem(); + public DummyFileProvider TestFileProvider { get; } = new DummyFileProvider(); public IOptions OptionsAccessor { @@ -23,7 +23,7 @@ namespace Microsoft.AspNet.Mvc.Razor { var options = new RazorViewEngineOptions { - FileSystem = TestFileSystem + FileProvider = TestFileProvider }; var mock = new Mock>(MockBehavior.Strict); @@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Mvc.Razor LastModified = DateTime.Now, }; - TestFileSystem.AddFile(fileInfo); + TestFileProvider.AddFile(fileInfo); } public void Sleep(ControllableExpiringFileInfoCache cache, int offsetMilliseconds) @@ -309,33 +309,33 @@ namespace Microsoft.AspNet.Mvc.Razor } [Fact] - public void GetDirectoryInfo_PassesThroughToUnderlyingFileSystem() + public void GetDirectoryInfo_PassesThroughToUnderlyingFileProvider() { // Arrange - var fileSystem = new Mock(); + var fileProvider = new Mock(); var expected = Mock.Of(); - fileSystem.Setup(f => f.GetDirectoryContents("/test-path")) + fileProvider.Setup(f => f.GetDirectoryContents("/test-path")) .Returns(expected) .Verifiable(); var options = new RazorViewEngineOptions { - FileSystem = fileSystem.Object + FileProvider = fileProvider.Object }; var accessor = new Mock>(); accessor.SetupGet(a => a.Options) .Returns(options); - var cachedFileSystem = new DefaultRazorFileSystemCache(accessor.Object); + var cachedFileProvider = new DefaultRazorFileProviderCache(accessor.Object); // Act - var result = cachedFileSystem.GetDirectoryContents("/test-path"); + var result = cachedFileProvider.GetDirectoryContents("/test-path"); // Assert Assert.Same(expected, result); - fileSystem.Verify(); + fileProvider.Verify(); } - public class ControllableExpiringFileInfoCache : DefaultRazorFileSystemCache + public class ControllableExpiringFileInfoCache : DefaultRazorFileProviderCache { public ControllableExpiringFileInfoCache(IOptions optionsAccessor) : base(optionsAccessor) @@ -367,7 +367,7 @@ namespace Microsoft.AspNet.Mvc.Razor _internalUtcNow = UtcNow.AddMilliseconds(milliSeconds); } } - public class DummyFileSystem : IFileSystem + public class DummyFileProvider : IFileProvider { private Dictionary _fileInfos = new Dictionary(StringComparer.OrdinalIgnoreCase); diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs index fce9da1018..35ea01eea7 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/Compilation/RazorCompilationServiceTest.cs @@ -3,7 +3,7 @@ using System.IO; using System.Linq; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Razor; using Microsoft.AspNet.Razor.Generator.Compiler; using Microsoft.AspNet.Razor.Parser; diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs index 12ffb3abc4..1bd68c30bd 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs @@ -9,13 +9,13 @@ namespace Microsoft.AspNet.Mvc.Razor public class RazorViewEngineOptionsTest { [Fact] - public void FileSystemThrows_IfNullIsAsseigned() + public void FileProviderThrows_IfNullIsAsseigned() { // Arrange var options = new RazorViewEngineOptions(); // Act and Assert - var ex = Assert.Throws(() => options.FileSystem = null); + var ex = Assert.Throws(() => options.FileProvider = null); Assert.Equal("value", ex.ParamName); } } diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/project.json b/test/Microsoft.AspNet.Mvc.Razor.Test/project.json index 3166e67205..707efbf704 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/project.json +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/project.json @@ -1,7 +1,7 @@ { "code": [ "**/*.cs", - "../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileSystem.cs", + "../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileProvider.cs", "../Microsoft.AspNet.Mvc.Razor.Host.Test/TestFileInfo.cs" ], "dependencies": { diff --git a/test/Microsoft.AspNet.Mvc.Test/RazorViewEngineOptionsSetupTest.cs b/test/Microsoft.AspNet.Mvc.Test/RazorViewEngineOptionsSetupTest.cs index eba5d78c7f..db14a89498 100644 --- a/test/Microsoft.AspNet.Mvc.Test/RazorViewEngineOptionsSetupTest.cs +++ b/test/Microsoft.AspNet.Mvc.Test/RazorViewEngineOptionsSetupTest.cs @@ -2,7 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Mvc.Razor; using Microsoft.Framework.Runtime; using Moq; @@ -13,7 +13,7 @@ namespace Microsoft.AspNet.Mvc public class RazorViewEngineOptionsSetupTest { [Fact] - public void RazorViewEngineOptionsSetup_SetsUpFileSystem() + public void RazorViewEngineOptionsSetup_SetsUpFileProvider() { // Arrange var options = new RazorViewEngineOptions(); @@ -26,8 +26,8 @@ namespace Microsoft.AspNet.Mvc optionsSetup.Configure(options); // Assert - Assert.NotNull(options.FileSystem); - Assert.IsType(options.FileSystem); + Assert.NotNull(options.FileProvider); + Assert.IsType(options.FileProvider); } } } \ No newline at end of file diff --git a/test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs b/test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs index 550d6b3894..636c9535d7 100644 --- a/test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs +++ b/test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs @@ -4,7 +4,7 @@ using System.IO; using System.Reflection; using Microsoft.AspNet.Builder; -using Microsoft.AspNet.FileSystems; +using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Mvc.Razor; using Microsoft.Framework.DependencyInjection; using Microsoft.AspNet.Routing; @@ -23,7 +23,7 @@ namespace RazorViewEngineOptionsWebsite services.Configure(options => { - options.FileSystem = new EmbeddedResourceFileSystem(GetType().GetTypeInfo().Assembly, "EmbeddedResources"); + options.FileProvider = new EmbeddedFileProvider(GetType().GetTypeInfo().Assembly, "EmbeddedResources"); }); }); diff --git a/test/WebSites/RazorViewEngineOptionsWebsite/wwwroot/readme.md b/test/WebSites/RazorViewEngineOptionsWebsite/wwwroot/readme.md index 9473b97ba9..d54d860d6b 100644 --- a/test/WebSites/RazorViewEngineOptionsWebsite/wwwroot/readme.md +++ b/test/WebSites/RazorViewEngineOptionsWebsite/wwwroot/readme.md @@ -1,4 +1,4 @@ RazorViewEngineOptionsWebSite === -This web site illustrates use cases for `RazorViewEngineOptions.FileSystem`. \ No newline at end of file +This web site illustrates use cases for `RazorViewEngineOptions.FileProvider`. \ No newline at end of file From 0e9091f0eb7ba7db0334b04d14e47163b14d726d Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Mon, 19 Jan 2015 10:14:10 -0800 Subject: [PATCH 111/118] [Fixes #1841] Change XML DCS and XmlSerializer output formatters to not derive from the base XmlOutputFormatter --- .../Formatters/FormattingUtilities.cs | 14 ++++ ...mlDataContractSerializerOutputFormatter.cs | 52 ++++++++++++- .../Formatters/XmlOutputFormatter.cs | 77 ------------------- .../XmlSerializerOutputFormatter.cs | 52 ++++++++++++- .../MvcOptionsExtensions.cs | 3 +- .../ActionResults/ObjectResultTests.cs | 6 +- ...aContractSerializerOutputFormatterTests.cs | 54 ++++++++++--- .../XmlSerializerOutputFormatterTests.cs | 54 ++++++++++--- 8 files changed, 202 insertions(+), 110 deletions(-) delete mode 100644 src/Microsoft.AspNet.Mvc.Core/Formatters/XmlOutputFormatter.cs diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormattingUtilities.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormattingUtilities.cs index db52d37d35..9bd88e22c7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormattingUtilities.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormattingUtilities.cs @@ -34,5 +34,19 @@ namespace Microsoft.AspNet.Mvc MaxStringContentLength = int.MaxValue }; } + + /// + /// Gets the default XmlWriterSettings. + /// + /// Default + public static XmlWriterSettings GetDefaultXmlWriterSettings() + { + return new XmlWriterSettings + { + OmitXmlDeclaration = true, + CloseOutput = false, + CheckCharacters = false + }; + } } } diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs index b7fcbbdd74..92a25916f9 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlDataContractSerializerOutputFormatter.cs @@ -2,9 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.Runtime.Serialization; using System.Threading.Tasks; using System.Xml; +using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc { @@ -12,14 +14,14 @@ namespace Microsoft.AspNet.Mvc /// This class handles serialization of objects /// to XML using /// - public class XmlDataContractSerializerOutputFormatter : XmlOutputFormatter + public class XmlDataContractSerializerOutputFormatter : OutputFormatter { /// /// Initializes a new instance of /// with default XmlWriterSettings /// - public XmlDataContractSerializerOutputFormatter() - : this(GetDefaultXmlWriterSettings()) + public XmlDataContractSerializerOutputFormatter() : + this(FormattingUtilities.GetDefaultXmlWriterSettings()) { } @@ -28,8 +30,39 @@ namespace Microsoft.AspNet.Mvc /// /// The settings to be used by the . public XmlDataContractSerializerOutputFormatter([NotNull] XmlWriterSettings writerSettings) - : base(writerSettings) { + SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM); + SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian); + + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml")); + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml")); + + WriterSettings = writerSettings; + } + + /// + /// Gets the settings to be used by the XmlWriter. + /// + public XmlWriterSettings WriterSettings { get; } + + /// + /// Gets the type of the object to be serialized. + /// + /// The declared type. + /// The runtime type. + /// The type of the object to be serialized. + protected virtual Type GetSerializableType(Type declaredType, Type runtimeType) + { + if (declaredType == null || + declaredType == typeof(object)) + { + if (runtimeType != null) + { + return runtimeType; + } + } + + return declaredType; } /// @@ -62,6 +95,17 @@ namespace Microsoft.AspNet.Mvc } } + /// + /// Creates a new instance of using the given stream and the . + /// + /// The stream on which the XmlWriter should operate on. + /// A new instance of + public virtual XmlWriter CreateXmlWriter([NotNull] Stream writeStream, + [NotNull] XmlWriterSettings xmlWriterSettings) + { + return XmlWriter.Create(writeStream, xmlWriterSettings); + } + /// public override Task WriteResponseBodyAsync([NotNull] OutputFormatterContext context) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlOutputFormatter.cs deleted file mode 100644 index d494d71726..0000000000 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlOutputFormatter.cs +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Xml; -using Microsoft.Net.Http.Headers; - -namespace Microsoft.AspNet.Mvc -{ - /// - /// Abstract base class from which all XML Output Formatters derive from. - /// - public abstract class XmlOutputFormatter : OutputFormatter - { - public XmlOutputFormatter([NotNull] XmlWriterSettings xmlWriterSettings) - { - SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM); - SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian); - - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml")); - SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml")); - - WriterSettings = xmlWriterSettings; - } - - /// - /// Gets or sets the settings to be used by the XmlWriter. - /// - public XmlWriterSettings WriterSettings { get; private set; } - - /// - /// Gets the type of the object to be serialized. - /// - /// The declared type. - /// The runtime type. - /// The type of the object to be serialized. - protected virtual Type GetSerializableType(Type declaredType, Type runtimeType) - { - if (declaredType == null || - declaredType == typeof(object)) - { - if (runtimeType != null) - { - return runtimeType; - } - } - - return declaredType; - } - - /// - /// Gets the default XmlWriterSettings. - /// - /// Default - public static XmlWriterSettings GetDefaultXmlWriterSettings() - { - return new XmlWriterSettings - { - OmitXmlDeclaration = true, - CloseOutput = false, - CheckCharacters = false - }; - } - - /// - /// Creates a new instance of using the given stream and the WriterSettings. - /// - /// The stream on which the XmlWriter should operate on. - /// A new instance of - public virtual XmlWriter CreateXmlWriter([NotNull] Stream writeStream, - [NotNull] XmlWriterSettings xmlWriterSettings) - { - return XmlWriter.Create(writeStream, xmlWriterSettings); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs index fc48093881..a2572a26a7 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/XmlSerializerOutputFormatter.cs @@ -2,9 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.Threading.Tasks; using System.Xml; using System.Xml.Serialization; +using Microsoft.Net.Http.Headers; namespace Microsoft.AspNet.Mvc { @@ -12,14 +14,14 @@ namespace Microsoft.AspNet.Mvc /// This class handles serialization of objects /// to XML using /// - public class XmlSerializerOutputFormatter : XmlOutputFormatter + public class XmlSerializerOutputFormatter : OutputFormatter { /// /// Initializes a new instance of /// with default XmlWriterSettings. /// - public XmlSerializerOutputFormatter() - : this(GetDefaultXmlWriterSettings()) + public XmlSerializerOutputFormatter() : + this(FormattingUtilities.GetDefaultXmlWriterSettings()) { } @@ -28,8 +30,39 @@ namespace Microsoft.AspNet.Mvc /// /// The settings to be used by the . public XmlSerializerOutputFormatter([NotNull] XmlWriterSettings writerSettings) - : base(writerSettings) { + SupportedEncodings.Add(Encodings.UTF8EncodingWithoutBOM); + SupportedEncodings.Add(Encodings.UTF16EncodingLittleEndian); + + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("application/xml")); + SupportedMediaTypes.Add(MediaTypeHeaderValue.Parse("text/xml")); + + WriterSettings = writerSettings; + } + + /// + /// Gets the settings to be used by the XmlWriter. + /// + public XmlWriterSettings WriterSettings { get; } + + /// + /// Gets the type of the object to be serialized. + /// + /// The declared type. + /// The runtime type. + /// The type of the object to be serialized. + protected virtual Type GetSerializableType(Type declaredType, Type runtimeType) + { + if (declaredType == null || + declaredType == typeof(object)) + { + if (runtimeType != null) + { + return runtimeType; + } + } + + return declaredType; } /// @@ -58,6 +91,17 @@ namespace Microsoft.AspNet.Mvc } } + /// + /// Creates a new instance of using the given stream and the . + /// + /// The stream on which the XmlWriter should operate on. + /// A new instance of + public virtual XmlWriter CreateXmlWriter([NotNull] Stream writeStream, + [NotNull] XmlWriterSettings xmlWriterSettings) + { + return XmlWriter.Create(writeStream, xmlWriterSettings); + } + /// public override Task WriteResponseBodyAsync([NotNull] OutputFormatterContext context) { diff --git a/src/Microsoft.AspNet.Mvc.Core/MvcOptionsExtensions.cs b/src/Microsoft.AspNet.Mvc.Core/MvcOptionsExtensions.cs index fdeb84115c..03e94f8701 100644 --- a/src/Microsoft.AspNet.Mvc.Core/MvcOptionsExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.Core/MvcOptionsExtensions.cs @@ -13,8 +13,7 @@ namespace Microsoft.AspNet.Mvc /// The MvcOptions public static void AddXmlDataContractSerializerFormatter([NotNull] this MvcOptions options) { - options.OutputFormatters.Add( - new XmlDataContractSerializerOutputFormatter(XmlOutputFormatter.GetDefaultXmlWriterSettings())); + options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter()); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectResultTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectResultTests.cs index bdcd2c1258..f89b9231f3 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectResultTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ActionResults/ObjectResultTests.cs @@ -492,7 +492,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults new HttpNoContentOutputFormatter(), new StringOutputFormatter(), new JsonOutputFormatter(), - new XmlDataContractSerializerOutputFormatter(XmlSerializerOutputFormatter.GetDefaultXmlWriterSettings()) + new XmlDataContractSerializerOutputFormatter() }; var response = GetMockHttpResponse(); @@ -533,7 +533,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults new HttpNoContentOutputFormatter(), new StringOutputFormatter(), new JsonOutputFormatter(), - new XmlDataContractSerializerOutputFormatter(XmlSerializerOutputFormatter.GetDefaultXmlWriterSettings()) + new XmlDataContractSerializerOutputFormatter() }; var response = GetMockHttpResponse(); @@ -568,7 +568,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test.ActionResults new HttpNoContentOutputFormatter(), new StringOutputFormatter(), new JsonOutputFormatter(), - new XmlDataContractSerializerOutputFormatter(XmlSerializerOutputFormatter.GetDefaultXmlWriterSettings()) + new XmlDataContractSerializerOutputFormatter() }; var response = GetMockHttpResponse(); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs index fc2a71c828..bf61734604 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlDataContractSerializerOutputFormatterTests.cs @@ -84,13 +84,51 @@ namespace Microsoft.AspNet.Mvc.Core Assert.True(outputFormatterContext.ActionContext.HttpContext.Response.Body.CanRead); } + [Fact] + public void DefaultConstructor_ExpectedWriterSettings_Created() + { + // Arrange and Act + var formatter = new XmlDataContractSerializerOutputFormatter(); + + // Assert + var writerSettings = formatter.WriterSettings; + Assert.NotNull(writerSettings); + Assert.True(writerSettings.OmitXmlDeclaration); + Assert.False(writerSettings.CloseOutput); + Assert.False(writerSettings.CheckCharacters); + } + + [Fact] + public async Task SuppliedWriterSettings_TakeAffect() + { + // Arrange + var writerSettings = FormattingUtilities.GetDefaultXmlWriterSettings(); + writerSettings.OmitXmlDeclaration = false; + var sampleInput = new DummyClass { SampleInt = 10 }; + var formatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); + var formatter = new XmlDataContractSerializerOutputFormatter(writerSettings); + var expectedOutput = ""+ + "" + + "10"; + + // Act + await formatter.WriteAsync(formatterContext); + + // Assert + Assert.Same(writerSettings, formatter.WriterSettings); + var responseStream = formatterContext.ActionContext.HttpContext.Response.Body; + Assert.NotNull(responseStream); + responseStream.Position = 0; + var actualOutput = new StreamReader(responseStream, Encoding.UTF8).ReadToEnd(); + Assert.Equal(expectedOutput, actualOutput); + } + [Fact] public async Task XmlDataContractSerializerOutputFormatterWritesSimpleTypes() { // Arrange var sampleInput = new DummyClass { SampleInt = 10 }; - var formatter = new XmlDataContractSerializerOutputFormatter( - XmlOutputFormatter.GetDefaultXmlWriterSettings()); + var formatter = new XmlDataContractSerializerOutputFormatter(); var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); // Act @@ -118,8 +156,7 @@ namespace Microsoft.AspNet.Mvc.Core sampleString = "TestLevelOne string" } }; - var formatter = new XmlDataContractSerializerOutputFormatter( - XmlOutputFormatter.GetDefaultXmlWriterSettings()); + var formatter = new XmlDataContractSerializerOutputFormatter(); var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); // Act @@ -168,8 +205,7 @@ namespace Microsoft.AspNet.Mvc.Core var sampleInput = new DummyClass { SampleInt = 10 }; var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType(), "application/xml; charset=utf-16"); - var formatter = new XmlDataContractSerializerOutputFormatter( - XmlOutputFormatter.GetDefaultXmlWriterSettings()); + var formatter = new XmlDataContractSerializerOutputFormatter(); formatter.WriterSettings.OmitXmlDeclaration = false; // Act @@ -190,8 +226,7 @@ namespace Microsoft.AspNet.Mvc.Core { // Arrange var sampleInput = new DummyClass { SampleInt = 10 }; - var formatter = new XmlDataContractSerializerOutputFormatter( - XmlOutputFormatter.GetDefaultXmlWriterSettings()); + var formatter = new XmlDataContractSerializerOutputFormatter(); formatter.WriterSettings.Indent = true; var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); @@ -213,8 +248,7 @@ namespace Microsoft.AspNet.Mvc.Core { // Arrange var sampleInput = new DummyClass { SampleInt = 10 }; - var formatter = new XmlDataContractSerializerOutputFormatter( - XmlOutputFormatter.GetDefaultXmlWriterSettings()); + var formatter = new XmlDataContractSerializerOutputFormatter(); var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); // Act diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs index b912327211..37fdeed886 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/XmlSerializerOutputFormatterTests.cs @@ -66,13 +66,51 @@ namespace Microsoft.AspNet.Mvc.Core Assert.True(outputFormatterContext.ActionContext.HttpContext.Response.Body.CanRead); } + [Fact] + public void DefaultConstructor_ExpectedWriterSettings_Created() + { + // Arrange and Act + var formatter = new XmlSerializerOutputFormatter(); + + // Assert + var writerSettings = formatter.WriterSettings; + Assert.NotNull(writerSettings); + Assert.True(writerSettings.OmitXmlDeclaration); + Assert.False(writerSettings.CloseOutput); + Assert.False(writerSettings.CheckCharacters); + } + + [Fact] + public async Task SuppliedWriterSettings_TakeAffect() + { + // Arrange + var writerSettings = FormattingUtilities.GetDefaultXmlWriterSettings(); + writerSettings.OmitXmlDeclaration = false; + var sampleInput = new DummyClass { SampleInt = 10 }; + var formatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); + var formatter = new XmlSerializerOutputFormatter(writerSettings); + var expectedOutput = "" + + "10"; + + // Act + await formatter.WriteAsync(formatterContext); + + // Assert + Assert.Same(writerSettings, formatter.WriterSettings); + var responseStream = formatterContext.ActionContext.HttpContext.Response.Body; + Assert.NotNull(responseStream); + responseStream.Position = 0; + var actualOutput = new StreamReader(responseStream, Encoding.UTF8).ReadToEnd(); + Assert.Equal(expectedOutput, actualOutput); + } + [Fact] public async Task XmlSerializerOutputFormatterWritesSimpleTypes() { // Arrange var sampleInput = new DummyClass { SampleInt = 10 }; - var formatter = new XmlSerializerOutputFormatter( - XmlOutputFormatter.GetDefaultXmlWriterSettings()); + var formatter = new XmlSerializerOutputFormatter(); var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); // Act @@ -101,8 +139,7 @@ namespace Microsoft.AspNet.Mvc.Core sampleString = "TestLevelOne string" } }; - var formatter = new XmlSerializerOutputFormatter( - XmlOutputFormatter.GetDefaultXmlWriterSettings()); + var formatter = new XmlSerializerOutputFormatter(); var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); // Act @@ -152,8 +189,7 @@ namespace Microsoft.AspNet.Mvc.Core var sampleInput = new DummyClass { SampleInt = 10 }; var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType(), "application/xml; charset=utf-16"); - var formatter = new XmlSerializerOutputFormatter( - XmlOutputFormatter.GetDefaultXmlWriterSettings()); + var formatter = new XmlSerializerOutputFormatter(); formatter.WriterSettings.OmitXmlDeclaration = false; // Act @@ -174,8 +210,7 @@ namespace Microsoft.AspNet.Mvc.Core { // Arrange var sampleInput = new DummyClass { SampleInt = 10 }; - var formatter = new XmlSerializerOutputFormatter( - XmlOutputFormatter.GetDefaultXmlWriterSettings()); + var formatter = new XmlSerializerOutputFormatter(); formatter.WriterSettings.Indent = true; var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); @@ -197,8 +232,7 @@ namespace Microsoft.AspNet.Mvc.Core { // Arrange var sampleInput = new DummyClass { SampleInt = 10 }; - var formatter = new XmlSerializerOutputFormatter( - XmlOutputFormatter.GetDefaultXmlWriterSettings()); + var formatter = new XmlSerializerOutputFormatter(); var outputFormatterContext = GetOutputFormatterContext(sampleInput, sampleInput.GetType()); // Act From edb520cb94f618c222c4ebd3fdb65f7b34564079 Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Tue, 20 Jan 2015 18:30:04 -0800 Subject: [PATCH 112/118] Rename SKIP_KRE_INSTALL to SKIP_DOTNET_INSTALL --- build.cmd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.cmd b/build.cmd index c8041fdd9d..220a1ff561 100644 --- a/build.cmd +++ b/build.cmd @@ -19,7 +19,7 @@ IF EXIST packages\KoreBuild goto run .nuget\NuGet.exe install KoreBuild -ExcludeVersion -o packages -nocache -pre .nuget\NuGet.exe install Sake -version 0.2 -o packages -ExcludeVersion -IF "%SKIP_KRE_INSTALL%"=="1" goto run +IF "%SKIP_DOTNET_INSTALL%"=="1" goto run CALL packages\KoreBuild\build\dotnetsdk upgrade -runtime CLR -x86 CALL packages\KoreBuild\build\dotnetsdk install default -runtime CoreCLR -x86 From d10ad558e5fab08428de2f1bc7cac2254e615ce7 Mon Sep 17 00:00:00 2001 From: Suhas Joshi Date: Wed, 21 Jan 2015 15:51:39 -0800 Subject: [PATCH 113/118] Updating to release NuGet.config --- NuGet.Config | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NuGet.Config b/NuGet.Config index f41e9c631d..2d3b0cb857 100644 --- a/NuGet.Config +++ b/NuGet.Config @@ -1,7 +1,7 @@  - + From 09928a28182e2438686772569cff8c2add1ea5b6 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 26 Nov 2014 11:25:19 -0800 Subject: [PATCH 114/118] Adds parameter information to ApiExplorer This change makes ApiDescription and ApiParameterDescription aware of all of the new features we built into model binding for enhanced DTO support (uber-binding). The main change is that instead of sticking just to the declared parameters on the action itself, we now traverse model metadata and break the parameters down based on their logical data source. This means that a model like the below will yield 3 parameters: public class ProductChangeCommandDTO { public int Id { get; set; } [FromBody] public ProductDetails Changes { get; set; } [FromQuery] public string AdminComments { get; set; } [FromServices] public IProductRepository Repository { get; set; } } The 'Repository' will be hidden, as it's not related to user input. Additionally, we treat different sources differently. In the above example, 'Changes' is from the body and will be treated as a leaf-node. However if you use nested DTOs that are bound from the query string (using [FromQuery]) or similar, we'll recursively explore to find as much structure as possible. This information is combined with data from the route template to give a much more complete picture than we ever could in the past for parameters, especially when DTO/Command pattern is used. --- .../ProductsAdminController.cs | 10 +- .../ApiExplorerSamples/ProductsController.cs | 7 + .../Models/ApiExplorerSamples/Order.cs | 27 + .../Models/ApiExplorerSamples/Product.cs | 2 + .../ApiExplorerSamples/ProductChangeDTO.cs | 14 + .../ApiExplorerSamples/UpdateProductDTO.cs | 20 + .../Views/ApiExplorer/_ApiDescription.cshtml | 70 +- .../wwwroot/Content/api-description.css | 22 + .../ControllerActionDescriptorBuilder.cs | 7 + .../Description/ApiParameterDescription.cs | 28 +- .../Description/ApiParameterRouteInfo.cs | 41 + .../Description/ApiParameterSource.cs | 127 ++- .../DefaultApiDescriptionProvider.cs | 540 +++++++++---- .../ParameterBinding/ModelBindingHelper.cs | 2 +- .../Properties/Resources.Designer.cs | 144 ++++ src/Microsoft.AspNet.Mvc.Core/Resources.resx | 27 + .../Internal/TypeHelper.cs | 15 + .../Metadata/ModelMetadata.cs | 5 + ...ControllerActionDescriptorProviderTests.cs | 45 +- .../DefaultApiDescriptionProviderTest.cs | 745 +++++++++++++++--- .../ApiExplorerTest.cs | 211 ++++- .../Metadata/ModelMetadataTest.cs | 73 +- .../ApiExplorerDataFilter.cs | 40 +- .../ApiExplorerParametersController.cs | 32 + .../Models/CustomerCommentsDTO.cs | 15 + .../Models/IOrderRepository.cs | 9 + .../ApiExplorerWebSite/Models/OrderDTO.cs | 23 + .../Models/OrderDetailsDTO.cs | 16 + .../ApiExplorerWebSite/Models/Product.cs | 3 + 29 files changed, 1981 insertions(+), 339 deletions(-) create mode 100644 samples/MvcSample.Web/Models/ApiExplorerSamples/Order.cs create mode 100644 samples/MvcSample.Web/Models/ApiExplorerSamples/ProductChangeDTO.cs create mode 100644 samples/MvcSample.Web/Models/ApiExplorerSamples/UpdateProductDTO.cs create mode 100644 src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterRouteInfo.cs create mode 100644 test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs create mode 100644 test/WebSites/ApiExplorerWebSite/Models/CustomerCommentsDTO.cs create mode 100644 test/WebSites/ApiExplorerWebSite/Models/IOrderRepository.cs create mode 100644 test/WebSites/ApiExplorerWebSite/Models/OrderDTO.cs create mode 100644 test/WebSites/ApiExplorerWebSite/Models/OrderDetailsDTO.cs diff --git a/samples/MvcSample.Web/Controllers/ApiExplorerSamples/ProductsAdminController.cs b/samples/MvcSample.Web/Controllers/ApiExplorerSamples/ProductsAdminController.cs index c39f132f10..8ca1263b54 100644 --- a/samples/MvcSample.Web/Controllers/ApiExplorerSamples/ProductsAdminController.cs +++ b/samples/MvcSample.Web/Controllers/ApiExplorerSamples/ProductsAdminController.cs @@ -12,13 +12,17 @@ namespace MvcSample.Web.ApiExplorerSamples public class ProductsAdminController : Controller { [HttpPut] - public void AddProduct([FromBody] Product product) + [Produces("application/json", Type = typeof(Product))] + public IActionResult AddProduct([FromBody] Product product) { + return null; } - [HttpPost("{id}")] - public void UpdateProduct([FromBody] Product product) + [HttpPost("{id?}")] + [Produces("application/json", Type = typeof(Product))] + public IActionResult UpdateProduct(UpdateProductDTO dto) { + return null; } [HttpPost("{id}/Stock")] diff --git a/samples/MvcSample.Web/Controllers/ApiExplorerSamples/ProductsController.cs b/samples/MvcSample.Web/Controllers/ApiExplorerSamples/ProductsController.cs index 4ca7567bd4..80fea76774 100644 --- a/samples/MvcSample.Web/Controllers/ApiExplorerSamples/ProductsController.cs +++ b/samples/MvcSample.Web/Controllers/ApiExplorerSamples/ProductsController.cs @@ -29,5 +29,12 @@ namespace MvcSample.Web.ApiExplorerSamples { return null; } + + [Produces("application/json", Type = typeof(ProductOrderConfirmation))] + [HttpPut("{order.acountId:int}/PlaceOrder")] + public IActionResult PlaceOrder(Order order) + { + return null; + } } } \ No newline at end of file diff --git a/samples/MvcSample.Web/Models/ApiExplorerSamples/Order.cs b/samples/MvcSample.Web/Models/ApiExplorerSamples/Order.cs new file mode 100644 index 0000000000..207742659a --- /dev/null +++ b/samples/MvcSample.Web/Models/ApiExplorerSamples/Order.cs @@ -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 Items { get; set; } + + [FromQuery] + public bool? IncludeWarranty { get; set; } + + public class OrderItem + { + public int ProductId { get; set; } + + public int Quantity { get; set; } + } + } +} \ No newline at end of file diff --git a/samples/MvcSample.Web/Models/ApiExplorerSamples/Product.cs b/samples/MvcSample.Web/Models/ApiExplorerSamples/Product.cs index 4b3c15ffeb..b3e659696d 100644 --- a/samples/MvcSample.Web/Models/ApiExplorerSamples/Product.cs +++ b/samples/MvcSample.Web/Models/ApiExplorerSamples/Product.cs @@ -5,6 +5,8 @@ namespace MvcSample.Web.ApiExplorerSamples { public class Product { + public int Id { get; set; } + public string Name { get; set; } public string Description { get; set; } diff --git a/samples/MvcSample.Web/Models/ApiExplorerSamples/ProductChangeDTO.cs b/samples/MvcSample.Web/Models/ApiExplorerSamples/ProductChangeDTO.cs new file mode 100644 index 0000000000..970d863b72 --- /dev/null +++ b/samples/MvcSample.Web/Models/ApiExplorerSamples/ProductChangeDTO.cs @@ -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; } + } +} \ No newline at end of file diff --git a/samples/MvcSample.Web/Models/ApiExplorerSamples/UpdateProductDTO.cs b/samples/MvcSample.Web/Models/ApiExplorerSamples/UpdateProductDTO.cs new file mode 100644 index 0000000000..99dde8198a --- /dev/null +++ b/samples/MvcSample.Web/Models/ApiExplorerSamples/UpdateProductDTO.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.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; } + } +} \ No newline at end of file diff --git a/samples/MvcSample.Web/Views/ApiExplorer/_ApiDescription.cshtml b/samples/MvcSample.Web/Views/ApiExplorer/_ApiDescription.cshtml index 708cc2da7b..f79624a5a8 100644 --- a/samples/MvcSample.Web/Views/ApiExplorer/_ApiDescription.cshtml +++ b/samples/MvcSample.Web/Views/ApiExplorer/_ApiDescription.cshtml @@ -2,38 +2,58 @@ @model ApiDescription
-

@(Model.HttpMethod ?? "*") - @(Model.RelativePath ?? "Unknown Url")

+

+ + @(Model.HttpMethod ?? "*") - @(Model.RelativePath ?? "Unknown Url") + +


-
For action: @Model.ActionDescriptor.DisplayName
-

Return Type: @(Model.ResponseType?.FullName ?? "Unknown Type")

+
Parameters:
@if (Model.ParameterDescriptions.Count > 0) { -

Parameters:

-
    - @foreach (var parameter in Model.ParameterDescriptions) - { -
  • - @parameter.Name - @(parameter?.Type?.FullName ?? "Unknown") - @parameter.Source.ToString() - @if (parameter.Constraints != null && parameter.Constraints.Any()) - { - var constraints = parameter.Constraints; - Write("-Constraint:" + string.Join(", ", constraints.Select(c => c.GetType()?.Name?.Replace("RouteConstraint", "")))); - } - - Default value: @(parameter?.DefaultValue ?? " none") -
  • - } -
+ + + + + + + + + + @foreach (var parameter in Model.ParameterDescriptions) + { + + + + + + } + +
NameData TypeSource
@parameter.Name@(parameter.Type?.FullName ?? "Unknown Type")@parameter.Source.Id
} +
Response Formats
@if (Model.SupportedResponseFormats.Count > 0) { -

Response Formats:

-
    - @foreach (var response in Model.SupportedResponseFormats) - { -
  • @response.MediaType.ToString() - @response.Formatter.GetType().Name
  • - } -
+ + + + + + + + + + @foreach (var response in Model.SupportedResponseFormats) + { + + + + + + } + +
Data TypeMedia TypeFormatter
@Model.ResponseType.FullName@response.MediaType.ToString()@response.Formatter.GetType().Name
}
\ No newline at end of file diff --git a/samples/MvcSample.Web/wwwroot/Content/api-description.css b/samples/MvcSample.Web/wwwroot/Content/api-description.css index 8204e08646..9ffca2b105 100644 --- a/samples/MvcSample.Web/wwwroot/Content/api-description.css +++ b/samples/MvcSample.Web/wwwroot/Content/api-description.css @@ -3,8 +3,30 @@ padding: 5px; } +.api-description h5 { + margin-top: 20px; +} + .api-description hr { border: 0px; border-top: 1px solid #D44B4B; margin: 0px; + margin-bottom: 10px; +} + +.api-description table { + width: 100%; +} + +.api-description thead { + border-bottom: 1px solid black; +} + +.api-description td { + border-bottom: 1px solid #CCCCCC; + color: #666666; + padding: 5px; +} +.api-description tr:last-child td { + border-bottom: none; } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs index 730a2841f2..d45aeb2ad0 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ControllerActionDescriptorBuilder.cs @@ -290,6 +290,13 @@ namespace Microsoft.AspNet.Mvc var apiExplorerIsVisible = action.ApiExplorer?.IsVisible ?? controller.ApiExplorer?.IsVisible ?? false; if (apiExplorerIsVisible) { + if (!IsAttributeRoutedAction(actionDescriptor)) + { + // ApiExplorer is only supported on attribute routed actions. + throw new InvalidOperationException(Resources.FormatApiExplorer_UnsupportedAction( + actionDescriptor.DisplayName)); + } + var apiExplorerActionData = new ApiDescriptionActionData() { GroupName = action.ApiExplorer?.GroupName ?? controller.ApiExplorer?.GroupName, diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs index 6a037efe0b..31ec6028f3 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterDescription.cs @@ -2,28 +2,38 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using Microsoft.AspNet.Mvc.ModelBinding; -using Microsoft.AspNet.Routing; namespace Microsoft.AspNet.Mvc.Description { + /// + /// A metadata description of an input to an API. + /// public class ApiParameterDescription { - public bool IsOptional { get; set; } - + /// + /// Gets or sets the . + /// public ModelMetadata ModelMetadata { get; set; } + /// + /// Gets or sets the name. + /// public string Name { get; set; } - public ParameterDescriptor ParameterDescriptor { get; set; } + /// + /// Gets or sets the . + /// + public ApiParameterRouteInfo RouteInfo { get; set; } + /// + /// Gets or sets the . + /// public ApiParameterSource Source { get; set; } - public IEnumerable Constraints { get; set; } - - public object DefaultValue { get; set; } - + /// + /// Gets or sets the parameter type. + /// public Type Type { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterRouteInfo.cs b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterRouteInfo.cs new file mode 100644 index 0000000000..7379f42048 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterRouteInfo.cs @@ -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 +{ + /// + /// A metadata description of routing information for an . + /// + public class ApiParameterRouteInfo + { + /// + /// Gets or sets the set of objects for the parameter. + /// + /// + /// Route constraints are only applied when a value is bound from a URL's path. See + /// for the data source considered. + /// + public IEnumerable Constraints { get; set; } + + /// + /// Gets or sets the default value for the parameter. + /// + public object DefaultValue { get; set; } + + /// + /// Gets a value indicating whether not a parameter is considered optional by routing. + /// + /// + /// An optional parameter is considered optional by the routing system. This does not imply + /// that the parameter is considered optional by the action. + /// + /// If the parameter uses for the value of + /// then the value may also come from the + /// URL query string or form data. + /// + public bool IsOptional { get; set; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterSource.cs b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterSource.cs index ce4121f295..8cd5f9886b 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterSource.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Description/ApiParameterSource.cs @@ -1,13 +1,130 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Diagnostics; +using Microsoft.AspNet.Mvc.Core; + namespace Microsoft.AspNet.Mvc.Description { - // This is a placeholder - see #886 - public enum ApiParameterSource + /// + /// A metadata description of the source of an for an HTTP request. + /// + [DebuggerDisplay("Source: {DisplayName}")] + public class ApiParameterSource : IEquatable { - Body, - Query, - Path + /// + /// An for the request body. + /// + public static readonly ApiParameterSource Body = new ApiParameterSource( + "Body", + Resources.ApiParameterSource_Body); + + /// + /// An for a custom model binder (unknown data source). + /// + public static readonly ApiParameterSource Custom = new ApiParameterSource( + "Custom", + Resources.ApiParameterSource_Custom); + + /// + /// An for the request form-data. + /// + public static readonly ApiParameterSource Form = new ApiParameterSource( + "Form", + Resources.ApiParameterSource_Form); + + /// + /// An for the request headers. + /// + public static readonly ApiParameterSource Header = new ApiParameterSource( + "Header", + Resources.ApiParameterSource_Header); + + /// + /// An for a parameter that should be hidden. Used when + /// a parameter cannot be set with user input. + /// + public static readonly ApiParameterSource Hidden = new ApiParameterSource( + "Hidden", + Resources.ApiParameterSource_Hidden); + + /// + /// An for model binding. Includes form-data, query-string + /// and headers from the request. + /// + public static readonly ApiParameterSource ModelBinding = new ApiParameterSource( + "ModelBinding", + Resources.ApiParameterSource_ModelBinding); + + /// + /// An for the request url path. + /// + public static readonly ApiParameterSource Path = new ApiParameterSource( + "Path", + Resources.ApiParameterSource_Path); + + /// + /// An for the request query-string. + /// + public static readonly ApiParameterSource Query = new ApiParameterSource( + "Query", + Resources.ApiParameterSource_Query); + + /// + /// Creates a new . + /// + /// The id. Used for comparison. + /// The display name. + public ApiParameterSource([NotNull] string id, string displayName) + { + Id = id; + DisplayName = displayName; + } + + /// + /// Gets the display name. + /// + public string DisplayName { get; } + + /// + /// Gets the id. + /// + public string Id { get; } + + /// + public bool Equals(ApiParameterSource other) + { + return other == null ? false : string.Equals(other.Id, Id, StringComparison.Ordinal); + } + + /// + public override bool Equals(object obj) + { + return Equals(obj as ApiParameterSource); + } + + /// + public override int GetHashCode() + { + return Id.GetHashCode(); + } + + /// + public static bool operator ==(ApiParameterSource s1, ApiParameterSource s2) + { + if (object.ReferenceEquals(s1, null)) + { + return object.ReferenceEquals(s2, null); ; + } + + return s1.Equals(s2); + } + + /// + public static bool operator !=(ApiParameterSource s1, ApiParameterSource s2) + { + return !(s1 == s2); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs index 221b31758d..e0c2e9f4d0 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Description/DefaultApiDescriptionProvider.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding; @@ -35,8 +34,8 @@ namespace Microsoft.AspNet.Mvc.Description IModelMetadataProvider modelMetadataProvider) { _formattersProvider = formattersProvider; - _modelMetadataProvider = modelMetadataProvider; _constraintResolver = constraintResolver; + _modelMetadataProvider = modelMetadataProvider; } /// @@ -81,7 +80,8 @@ namespace Microsoft.AspNet.Mvc.Description var templateParameters = parsedTemplate?.Parameters?.ToList() ?? new List(); - GetParameters(apiDescription, action.Parameters, templateParameters); + var parameterContext = new ApiParameterContext(_modelMetadataProvider, action, templateParameters); + apiDescription.ParameterDescriptions.AddRange(GetParameters(parameterContext)); var responseMetadataAttributes = GetResponseMetadataAttributes(action); @@ -124,43 +124,90 @@ namespace Microsoft.AspNet.Mvc.Description return apiDescription; } - private void GetParameters( - ApiDescription apiDescription, - IList parameterDescriptors, - IList templateParameters) + private IList GetParameters(ApiParameterContext context) { - if (parameterDescriptors != null) + // First, get parameters from the model-binding/parameter-binding side of the world. + if (context.ActionDescriptor.Parameters != null) { - foreach (var parameter in parameterDescriptors) + foreach (var actionParameter in context.ActionDescriptor.Parameters) { - // Process together parameters that appear on the path template and on the - // action descriptor and do not come from the body. - TemplatePart templateParameter = null; - if (parameter.BinderMetadata as IFormatterBinderMetadata == null) - { - templateParameter = templateParameters - .FirstOrDefault(p => p.Name.Equals(parameter.Name, StringComparison.OrdinalIgnoreCase)); + var visitor = new PseudoModelBindingVisitor(context, actionParameter); + visitor.WalkParameter(); + } + } - if (templateParameter != null) + for (var i = context.Results.Count - 1; i >= 0; i--) + { + // Remove any 'hidden' parameters. These are things that can't come from user input, + // so they aren't worth showing. + if (context.Results[i].Source == ApiParameterSource.Hidden) + { + context.Results.RemoveAt(i); + } + } + + // Next, we want to join up any route parameters with those discovered from the action's parameters. + var routeParameters = new Dictionary(StringComparer.OrdinalIgnoreCase); + foreach (var routeParameter in context.RouteParameters) + { + routeParameters.Add(routeParameter.Name, CreateRouteInfo(routeParameter)); + } + + foreach (var parameter in context.Results) + { + if (parameter.Source == ApiParameterSource.Path || + parameter.Source == ApiParameterSource.ModelBinding || + parameter.Source == ApiParameterSource.Custom) + { + ApiParameterRouteInfo routeInfo; + if (routeParameters.TryGetValue(parameter.Name, out routeInfo)) + { + parameter.RouteInfo = routeInfo; + routeParameters.Remove(parameter.Name); + + if (parameter.Source == ApiParameterSource.ModelBinding && + !parameter.RouteInfo.IsOptional) { - templateParameters.Remove(templateParameter); + // If we didn't see any information about the parameter, but we have + // a route parameter that matches, let's switch it to path. + parameter.Source = ApiParameterSource.Path; } } - - apiDescription.ParameterDescriptions.Add(GetParameter(parameter, templateParameter)); } } - if (templateParameters.Count > 0) + // Lastly, create a parameter representation for each route parameter that did not find + // a partner. + foreach (var routeParameter in routeParameters) { - // Process parameters that only appear on the path template if any. - foreach (var templateParameter in templateParameters) + context.Results.Add(new ApiParameterDescription() { - var parameterDescription = - GetParameter(parameterDescriptor: null, templateParameter: templateParameter); - apiDescription.ParameterDescriptions.Add(parameterDescription); + Name = routeParameter.Key, + RouteInfo = routeParameter.Value, + Source = ApiParameterSource.Path, + }); + } + + return context.Results; + } + + private ApiParameterRouteInfo CreateRouteInfo(TemplatePart routeParameter) + { + var constraints = new List(); + if (routeParameter.InlineConstraints != null) + { + foreach (var constraint in routeParameter.InlineConstraints) + { + constraints.Add(_constraintResolver.ResolveConstraint(constraint.Constraint)); } } + + return new ApiParameterRouteInfo() + { + Constraints = constraints, + DefaultValue = routeParameter.DefaultValue, + IsOptional = routeParameter.IsOptional || routeParameter.DefaultValue != null, + }; } private IEnumerable GetHttpMethods(ControllerActionDescriptor action) @@ -216,117 +263,6 @@ namespace Microsoft.AspNet.Mvc.Description return string.Join("/", segments); } - private ApiParameterDescription GetParameter( - ParameterDescriptor parameterDescriptor, - TemplatePart templateParameter) - { - // This is a placeholder based on currently available functionality for parameters. See #886. - ApiParameterDescription parameterDescription = null; - - if (templateParameter != null && parameterDescriptor == null) - { - // The parameter is part of the route template but not part of the ActionDescriptor. - - // For now if a parameter is part of the template we will asume its value comes from the path. - // We will be more accurate when we implement #886. - parameterDescription = CreateParameterFromTemplate(templateParameter); - } - else if (templateParameter != null && parameterDescriptor != null) - { - // The parameter is part of the route template and part of the ActionDescriptor. - parameterDescription = CreateParameterFromTemplateAndParameterDescriptor( - templateParameter, - parameterDescriptor); - } - else if (templateParameter == null && parameterDescriptor != null) - { - // The parameter is part of the ActionDescriptor but is not part of the route template. - parameterDescription = CreateParameterFromParameterDescriptor(parameterDescriptor); - } - else - { - // We will never call this method with templateParameter == null && parameterDescriptor == null - Debug.Assert(parameterDescriptor != null); - } - - if (parameterDescription.Type != null) - { - parameterDescription.ModelMetadata = _modelMetadataProvider.GetMetadataForType( - modelAccessor: null, - modelType: parameterDescription.Type); - } - - return parameterDescription; - } - - private static ApiParameterDescription CreateParameterFromParameterDescriptor(ParameterDescriptor parameter) - { - var resourceParameter = new ApiParameterDescription - { - 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 = IsOptionalParameter(templateParameter), - Name = parameter.Name, - ParameterDescriptor = parameter, - Constraints = GetConstraints(_constraintResolver, templateParameter.InlineConstraints), - DefaultValue = templateParameter.DefaultValue, - Type = parameter.ParameterType, - }; - - return resourceParameter; - } - - private static IEnumerable GetConstraints( - IInlineConstraintResolver constraintResolver, - IEnumerable 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 GetResponseFormats( ControllerActionDescriptor action, IApiResponseMetadataProvider[] responseMetadataAttributes, @@ -442,7 +378,7 @@ namespace Microsoft.AspNet.Mvc.Description } // This technique for enumerating filters will intentionally ignore any filter that is an IFilterFactory - // for a filter that implements IApiResponseMetadataProvider. + // while searching for a filter that implements IApiResponseMetadataProvider. // // The workaround for that is to implement the metadata interface on the IFilterFactory. return action.FilterDescriptors @@ -450,5 +386,333 @@ namespace Microsoft.AspNet.Mvc.Description .OfType() .ToArray(); } + + private class ApiParameterContext + { + public ApiParameterContext( + IModelMetadataProvider metadataProvider, + ControllerActionDescriptor actionDescriptor, + IReadOnlyList routeParameters) + { + MetadataProvider = metadataProvider; + ActionDescriptor = actionDescriptor; + RouteParameters = routeParameters; + + Results = new List(); + } + + public ControllerActionDescriptor ActionDescriptor { get; } + + public IModelMetadataProvider MetadataProvider { get; } + + public IList Results { get; } + + public IReadOnlyList RouteParameters { get; } + } + + private class PseudoModelBindingVisitor + { + public PseudoModelBindingVisitor(ApiParameterContext context, ParameterDescriptor parameter) + { + Context = context; + Parameter = parameter; + + Visited = new HashSet(); + } + + public ApiParameterContext Context { get; } + + public ParameterDescriptor Parameter { get; } + + // Avoid infinite recursion by tracking properties. + private HashSet 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)); + } + } + + /// + /// Visits a node in a model, and attempts to create for any + /// model properties where we can definitely compute an answer. + /// + /// The metadata for the model. + /// The from the ambient context. + /// The current name prefix (to prepend to property names). + /// + /// true if the set of objects were created for the model. + /// false if no objects were created for the model. + /// + /// + /// 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. + /// + 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(); + + // 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 + { + 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(); + } + } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs index 19080e1c64..a09b3f8a36 100644 --- a/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs +++ b/src/Microsoft.AspNet.Mvc.Core/ParameterBinding/ModelBindingHelper.cs @@ -246,7 +246,7 @@ namespace Microsoft.AspNet.Mvc StringComparison.OrdinalIgnoreCase); } - private static string CreatePropertyModelName(string prefix, string propertyName) + public static string CreatePropertyModelName(string prefix, string propertyName) { if (string.IsNullOrEmpty(prefix)) { diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs index e102501345..08263a8077 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs @@ -1562,6 +1562,150 @@ namespace Microsoft.AspNet.Mvc.Core return string.Format(CultureInfo.CurrentCulture, GetString("ResponseCache_SpecifyDuration"), p0, p1); } + /// + /// Body + /// + internal static string ApiParameterSource_Body + { + get { return GetString("ApiParameterSource_Body"); } + } + + /// + /// Body + /// + internal static string FormatApiParameterSource_Body() + { + return GetString("ApiParameterSource_Body"); + } + + /// + /// Custom + /// + internal static string ApiParameterSource_Custom + { + get { return GetString("ApiParameterSource_Custom"); } + } + + /// + /// Custom + /// + internal static string FormatApiParameterSource_Custom() + { + return GetString("ApiParameterSource_Custom"); + } + + /// + /// Header + /// + internal static string ApiParameterSource_Header + { + get { return GetString("ApiParameterSource_Header"); } + } + + /// + /// Header + /// + internal static string FormatApiParameterSource_Header() + { + return GetString("ApiParameterSource_Header"); + } + + /// + /// Hidden + /// + internal static string ApiParameterSource_Hidden + { + get { return GetString("ApiParameterSource_Hidden"); } + } + + /// + /// Hidden + /// + internal static string FormatApiParameterSource_Hidden() + { + return GetString("ApiParameterSource_Hidden"); + } + + /// + /// ModelBinding + /// + internal static string ApiParameterSource_ModelBinding + { + get { return GetString("ApiParameterSource_ModelBinding"); } + } + + /// + /// ModelBinding + /// + internal static string FormatApiParameterSource_ModelBinding() + { + return GetString("ApiParameterSource_ModelBinding"); + } + + /// + /// Path + /// + internal static string ApiParameterSource_Path + { + get { return GetString("ApiParameterSource_Path"); } + } + + /// + /// Path + /// + internal static string FormatApiParameterSource_Path() + { + return GetString("ApiParameterSource_Path"); + } + + /// + /// Query + /// + internal static string ApiParameterSource_Query + { + get { return GetString("ApiParameterSource_Query"); } + } + + /// + /// Query + /// + internal static string FormatApiParameterSource_Query() + { + return GetString("ApiParameterSource_Query"); + } + + /// + /// Form + /// + internal static string ApiParameterSource_Form + { + get { return GetString("ApiParameterSource_Form"); } + } + + /// + /// Form + /// + internal static string FormatApiParameterSource_Form() + { + return GetString("ApiParameterSource_Form"); + } + + /// + /// The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer. + /// + internal static string ApiExplorer_UnsupportedAction + { + get { return GetString("ApiExplorer_UnsupportedAction"); } + } + + /// + /// The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer. + /// + internal static string FormatApiExplorer_UnsupportedAction(object p0) + { + return string.Format(CultureInfo.CurrentCulture, GetString("ApiExplorer_UnsupportedAction"), p0); + } + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx index ccc5ba71be..51c569c1c4 100644 --- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx @@ -418,4 +418,31 @@ If the '{0}' property is not set to true, '{1}' property must be specified. + + Body + + + Custom + + + Header + + + Hidden + + + ModelBinding + + + Path + + + Query + + + Form + + + The action '{0}' has ApiExplorer enabled, but is using conventional routing. Only actions which use attribute routing support ApiExplorer. + \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/TypeHelper.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/TypeHelper.cs index 760382cb51..20abd96240 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/TypeHelper.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Internal/TypeHelper.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; +using System.Collections.Generic; using System.ComponentModel; using System.Reflection; @@ -24,5 +26,18 @@ namespace Microsoft.AspNet.Mvc.ModelBinding { return TypeDescriptor.GetConverter(type).CanConvertFrom(typeof(string)); } + + internal static bool IsCollectionType(Type type) + { + if (type == typeof(string)) + { + // Even though string implements IEnumerable, we don't really think of it + // as a collection for the purposes of model binding. + return false; + } + + // We only need to look for IEnumerable, because IEnumerable extends it. + return typeof(IEnumerable).IsAssignableFrom(type); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs index 53d129b3f2..23a558f5d3 100644 --- a/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs +++ b/src/Microsoft.AspNet.Mvc.ModelBinding/Metadata/ModelMetadata.cs @@ -137,6 +137,11 @@ namespace Microsoft.AspNet.Mvc.ModelBinding /// public virtual bool HideSurroundingHtml { get; set; } + public virtual bool IsCollectionType + { + get { return TypeHelper.IsCollectionType(ModelType); } + } + public virtual bool IsComplexType { get { return !TypeHelper.HasStringConverter(ModelType); } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs index 168162a253..ecf66d8348 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerActionDescriptorProviderTests.cs @@ -1092,6 +1092,24 @@ namespace Microsoft.AspNet.Mvc.Test Assert.Equal("Store", action.GetProperty().GroupName); } + [Theory] + [InlineData("A", typeof(ApiExplorerEnabledConventionalRoutedController))] + [InlineData("A", typeof(ApiExplorerEnabledActionConventionalRoutedController))] + public void ApiExplorer_ThrowsForContentionalRouting(string actionName, Type type) + { + var expected = string.Format( + "The action '{0}.{1}' has ApiExplorer enabled, but is using conventional routing. " + + "Only actions which use attribute routing support ApiExplorer.", + type.FullName, actionName); + + // Arrange + var provider = GetProvider(type.GetTypeInfo()); + + // Act & Assert + var ex = Assert.Throws(() => provider.GetDescriptors()); + Assert.Equal(expected, ex.Message); + } + // Verifies the sequence of conventions running [Fact] public void ApplyConventions_RunsInOrderOfDecreasingScope() @@ -1743,29 +1761,34 @@ namespace Microsoft.AspNet.Mvc.Test public int Name { get; set; } } + [Route("AttributeRouting/IsRequired/ForApiExplorer")] private class ApiExplorerNotVisibleController { public void Edit() { } } + [Route("AttributeRouting/IsRequired/ForApiExplorer")] [ApiExplorerSettings()] private class ApiExplorerVisibleController { public void Edit() { } } + [Route("AttributeRouting/IsRequired/ForApiExplorer")] [ApiExplorerSettings(IgnoreApi = true)] private class ApiExplorerExplicitlyNotVisibleController { public void Edit() { } } + [Route("AttributeRouting/IsRequired/ForApiExplorer")] private class ApiExplorerExplicitlyNotVisibleOnActionController { [ApiExplorerSettings(IgnoreApi = true)] public void Edit() { } } + [Route("AttributeRouting/IsRequired/ForApiExplorer")] [ApiExplorerSettings(IgnoreApi = true)] private class ApiExplorerVisibilityOverrideController { @@ -1775,25 +1798,28 @@ namespace Microsoft.AspNet.Mvc.Test public void Create() { } } + [Route("AttributeRouting/IsRequired/ForApiExplorer")] [ApiExplorerSettings(GroupName = "Store")] private class ApiExplorerNameOnControllerController { public void Edit() { } } - + [Route("AttributeRouting/IsRequired/ForApiExplorer")] private class ApiExplorerNameOnActionController { [ApiExplorerSettings(GroupName = "Blog")] public void Edit() { } } + [Route("AttributeRouting/IsRequired/ForApiExplorer")] [ApiExplorerSettings()] private class ApiExplorerNoNameController { public void Edit() { } } + [Route("AttributeRouting/IsRequired/ForApiExplorer")] [ApiExplorerSettings(GroupName = "Store")] private class ApiExplorerNameOverrideController { @@ -1851,5 +1877,22 @@ namespace Microsoft.AspNet.Mvc.Test private class ConstraintAttribute : Attribute, IActionConstraintMetadata { } + + [ApiExplorerSettings(GroupName = "Default")] + private class ApiExplorerEnabledConventionalRoutedController : Controller + { + public void A() + { + } + } + + [ApiExplorerSettings(IgnoreApi = true)] + private class ApiExplorerEnabledActionConventionalRoutedController : Controller + { + [ApiExplorerSettings(GroupName = "Default")] + public void A() + { + } + } } } diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs index 1e10dbdb84..5117d68c81 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/Description/DefaultApiDescriptionProviderTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNet.Mvc.ModelBinding; @@ -110,49 +111,6 @@ namespace Microsoft.AspNet.Mvc.Description Assert.Single(descriptions, d => d.HttpMethod == "GET"); } - // This is a test for the placeholder behavior - see #886 - [Fact] - public void GetApiDescription_PopulatesParameters() - { - // Arrange - var action = CreateActionDescriptor(); - action.Parameters = new List() - { - new ParameterDescriptor() - { - Name = "id", - ParameterType = typeof(int), - }, - new ParameterDescriptor() - { - BinderMetadata = new FromBodyAttribute(), - Name = "username", - ParameterType = typeof(string), - } - }; - - // Act - var descriptions = GetApiDescriptions(action); - - // Assert - var description = Assert.Single(descriptions); - Assert.Equal(2, description.ParameterDescriptions.Count); - - var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "id"); - Assert.NotNull(id.ModelMetadata); - Assert.False(id.IsOptional); - Assert.Same(action.Parameters[0], id.ParameterDescriptor); - Assert.Equal(ApiParameterSource.Query, id.Source); - Assert.Equal(typeof(int), id.Type); - - var username = Assert.Single(description.ParameterDescriptions, p => p.Name == "username"); - Assert.NotNull(username.ModelMetadata); - Assert.False(username.IsOptional); - Assert.Same(action.Parameters[1], username.ParameterDescriptor); - Assert.Equal(ApiParameterSource.Body, username.Source); - Assert.Equal(typeof(string), username.Type); - } - [Theory] [InlineData("api/products/{id}", false, null, null)] [InlineData("api/products/{id?}", true, null, null)] @@ -181,22 +139,21 @@ namespace Microsoft.AspNet.Mvc.Description var parameter = Assert.Single(description.ParameterDescriptions); Assert.Equal(ApiParameterSource.Path, parameter.Source); - Assert.Equal(isOptional, parameter.IsOptional); + Assert.Equal(isOptional, parameter.RouteInfo.IsOptional); Assert.Equal("id", parameter.Name); - Assert.Null(parameter.ParameterDescriptor); if (constraintType != null) { - Assert.IsType(constraintType, Assert.Single(parameter.Constraints)); + Assert.IsType(constraintType, Assert.Single(parameter.RouteInfo.Constraints)); } if (defaultValue != null) { - Assert.Equal(defaultValue, parameter.DefaultValue); + Assert.Equal(defaultValue, parameter.RouteInfo.DefaultValue); } else { - Assert.Null(parameter.DefaultValue); + Assert.Null(parameter.RouteInfo.DefaultValue); } } @@ -217,15 +174,10 @@ namespace Microsoft.AspNet.Mvc.Description object defaultValue) { // Arrange - var action = CreateActionDescriptor(); + var action = CreateActionDescriptor(nameof(FromRouting)); action.AttributeRouteInfo = new AttributeRouteInfo { Template = template }; - var parameterDescriptor = new ParameterDescriptor - { - Name = "id", - ParameterType = typeof(int), - }; - action.Parameters = new List { parameterDescriptor }; + var parameterDescriptor = action.Parameters[0]; // Act var descriptions = GetApiDescriptions(action); @@ -235,82 +187,76 @@ namespace Microsoft.AspNet.Mvc.Description var parameter = Assert.Single(description.ParameterDescriptions); Assert.Equal(ApiParameterSource.Path, parameter.Source); - Assert.Equal(isOptional, parameter.IsOptional); + Assert.Equal(isOptional, parameter.RouteInfo.IsOptional); Assert.Equal("id", parameter.Name); - Assert.Equal(parameterDescriptor, parameter.ParameterDescriptor); if (constraintType != null) { - Assert.IsType(constraintType, Assert.Single(parameter.Constraints)); + Assert.IsType(constraintType, Assert.Single(parameter.RouteInfo.Constraints)); } if (defaultValue != null) { - Assert.Equal(defaultValue, parameter.DefaultValue); + Assert.Equal(defaultValue, parameter.RouteInfo.DefaultValue); } else { - Assert.Null(parameter.DefaultValue); + Assert.Null(parameter.RouteInfo.DefaultValue); } } + // Only a parameter which comes from a route or model binding or unknown should + // include route info. [Theory] - [InlineData("api/products/{id}", false, null, null)] - [InlineData("api/products/{id?}", true, null, null)] - [InlineData("api/products/{id=5}", true, null, "5")] - [InlineData("api/products/{id:int}", false, typeof(IntRouteConstraint), null)] - [InlineData("api/products/{id:int?}", true, typeof(IntRouteConstraint), null)] - [InlineData("api/products/{id:int=5}", true, typeof(IntRouteConstraint), "5")] - [InlineData("api/products/{*id}", false, null, null)] - [InlineData("api/products/{*id:int}", false, typeof(IntRouteConstraint), null)] - [InlineData("api/products/{*id:int=5}", true, typeof(IntRouteConstraint), "5")] - public void GetApiDescription_CreatesDifferentParameters_IfParameterDescriptorIsFromBody( + [InlineData("api/products/{id}", nameof(FromBody), "Body")] + [InlineData("api/products/{id}", nameof(FromHeader), "Header")] + public void GetApiDescription_ParameterDescription_DoesNotIncludeRouteInfo( string template, - bool isOptional, - Type constraintType, - object defaultValue) + string methodName, + string source) { // Arrange - var action = CreateActionDescriptor(); + var action = CreateActionDescriptor(methodName); action.AttributeRouteInfo = new AttributeRouteInfo { Template = template }; - var parameterDescriptor = new ParameterDescriptor - { - BinderMetadata = new FromBodyAttribute(), - Name = "id", - ParameterType = typeof(int), - }; - action.Parameters = new List { parameterDescriptor }; - // Act var descriptions = GetApiDescriptions(action); // Assert var description = Assert.Single(descriptions); + var parameters = description.ParameterDescriptions; - var bodyParameter = Assert.Single(description.ParameterDescriptions, p => p.Source == ApiParameterSource.Body); - Assert.False(bodyParameter.IsOptional); - Assert.Equal("id", bodyParameter.Name); - Assert.Equal(parameterDescriptor, bodyParameter.ParameterDescriptor); + var id = Assert.Single(parameters, p => p.Source == new ApiParameterSource(source, displayName: null)); + Assert.Null(id.RouteInfo); + } - var pathParameter = Assert.Single(description.ParameterDescriptions, p => p.Source == ApiParameterSource.Path); - Assert.Equal(isOptional, pathParameter.IsOptional); - Assert.Equal("id", pathParameter.Name); - Assert.Null(pathParameter.ParameterDescriptor); + // Only a parameter which comes from a route or model binding or unknown should + // include route info. If the source is model binding, we also check if it's an optional + // parameter, and only change the source if it's a match. + [Theory] + [InlineData("api/products/{id}", nameof(FromRouting), "Path")] + [InlineData("api/products/{id}", nameof(FromModelBinding), "Path")] + [InlineData("api/products/{id?}", nameof(FromModelBinding), "ModelBinding")] + [InlineData("api/products/{id=5}", nameof(FromModelBinding), "ModelBinding")] + [InlineData("api/products/{id}", nameof(FromCustom), "Custom")] + public void GetApiDescription_ParameterDescription_IncludesRouteInfo( + string template, + string methodName, + string source) + { + // Arrange + var action = CreateActionDescriptor(methodName); + action.AttributeRouteInfo = new AttributeRouteInfo { Template = template }; - if (constraintType != null) - { - Assert.IsType(constraintType, Assert.Single(pathParameter.Constraints)); - } + // Act + var descriptions = GetApiDescriptions(action); - if (defaultValue != null) - { - Assert.Equal(defaultValue, pathParameter.DefaultValue); - } - else - { - Assert.Null(pathParameter.DefaultValue); - } + // Assert + var description = Assert.Single(descriptions); + var parameters = description.ParameterDescriptions; + + var id = Assert.Single(parameters, p => p.Source == new ApiParameterSource(source, displayName: null)); + Assert.NotNull(id.RouteInfo); } [Theory] @@ -322,23 +268,16 @@ namespace Microsoft.AspNet.Mvc.Description bool expectedOptional) { // Arrange - var action = CreateActionDescriptor(); + var action = CreateActionDescriptor(nameof(FromRouting)); action.AttributeRouteInfo = new AttributeRouteInfo { Template = template }; - var parameterDescriptor = new ParameterDescriptor - { - Name = "id", - ParameterType = typeof(int), - }; - action.Parameters = new List { parameterDescriptor }; - // Act var descriptions = GetApiDescriptions(action); // Assert var description = Assert.Single(descriptions); var parameter = Assert.Single(description.ParameterDescriptions); - Assert.Equal(expectedOptional, parameter.IsOptional); + Assert.Equal(expectedOptional, parameter.RouteInfo.IsOptional); } [Theory] @@ -380,11 +319,11 @@ namespace Microsoft.AspNet.Mvc.Description var description = Assert.Single(descriptions); var id1 = Assert.Single(description.ParameterDescriptions, p => p.Name == "id1"); Assert.Equal(ApiParameterSource.Path, id1.Source); - Assert.Empty(id1.Constraints); + Assert.Empty(id1.RouteInfo.Constraints); var id2 = Assert.Single(description.ParameterDescriptions, p => p.Name == "id2"); Assert.Equal(ApiParameterSource.Path, id2.Source); - Assert.IsType(Assert.Single(id2.Constraints)); + Assert.IsType(Assert.Single(id2.RouteInfo.Constraints)); } [Fact] @@ -584,6 +523,378 @@ namespace Microsoft.AspNet.Mvc.Description Assert.Same(formatters[0], formats[0].Formatter); } + [Fact] + public void GetApiDescription_ParameterDescription_ModelBoundParameter() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsProduct)); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var parameter = Assert.Single(description.ParameterDescriptions); + Assert.Equal("product", parameter.Name); + Assert.Same(ApiParameterSource.ModelBinding, parameter.Source); + } + + [Fact] + public void GetApiDescription_ParameterDescription_SourceFromRouteData() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsId_Route)); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var parameter = Assert.Single(description.ParameterDescriptions); + Assert.Equal("id", parameter.Name); + Assert.Same(ApiParameterSource.Path, parameter.Source); + } + + [Fact] + public void GetApiDescription_ParameterDescription_SourceFromQueryString() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsId_Query)); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var parameter = Assert.Single(description.ParameterDescriptions); + Assert.Equal("id", parameter.Name); + Assert.Same(ApiParameterSource.Query, parameter.Source); + } + + [Fact] + public void GetApiDescription_ParameterDescription_SourceFromBody() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsProduct_Body)); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var parameter = Assert.Single(description.ParameterDescriptions); + Assert.Equal("product", parameter.Name); + Assert.Same(ApiParameterSource.Body, parameter.Source); + } + + [Fact] + public void GetApiDescription_ParameterDescription_SourceFromForm() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsProduct_Form)); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var parameter = Assert.Single(description.ParameterDescriptions); + Assert.Equal("product", parameter.Name); + Assert.Same(ApiParameterSource.Form, parameter.Source); + } + + [Fact] + public void GetApiDescription_ParameterDescription_SourceFromHeader() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsId_Header)); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var parameter = Assert.Single(description.ParameterDescriptions); + Assert.Equal("id", parameter.Name); + Assert.Same(ApiParameterSource.Header, parameter.Source); + } + + // 'Hidden' parameters are hidden (not returned). + [Fact] + public void GetApiDescription_ParameterDescription_SourceFromServices() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsFormatters_Services)); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + Assert.Empty(description.ParameterDescriptions); + } + + [Fact] + public void GetApiDescription_ParameterDescription_SourceFromCustomModelBinder() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsProduct_Custom)); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var parameter = Assert.Single(description.ParameterDescriptions); + Assert.Equal("product", parameter.Name); + Assert.Same(ApiParameterSource.Custom, parameter.Source); + } + + [Fact] + public void GetApiDescription_ParameterDescription_SourceFromDefault_ModelBinderAttribute_WithoutBinderType() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsProduct_Default)); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var parameter = Assert.Single(description.ParameterDescriptions); + Assert.Equal("product", parameter.Name); + Assert.Same(ApiParameterSource.ModelBinding, parameter.Source); + } + + [Fact] + public void GetApiDescription_ParameterDescription_ComplexDTO() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsProductChangeDTO)); + var parameterDescriptor = action.Parameters.Single(); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + Assert.Equal(4, description.ParameterDescriptions.Count); + + var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id"); + Assert.Same(ApiParameterSource.Path, id.Source); + Assert.Equal(typeof(int), id.Type); + + var product = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product"); + Assert.Same(ApiParameterSource.Body, product.Source); + Assert.Equal(typeof(Product), product.Type); + + var userId = Assert.Single(description.ParameterDescriptions, p => p.Name == "UserId"); + Assert.Same(ApiParameterSource.Header, userId.Source); + Assert.Equal(typeof(string), userId.Type); + + var comments = Assert.Single(description.ParameterDescriptions, p => p.Name == "Comments"); + Assert.Same(ApiParameterSource.ModelBinding, comments.Source); + Assert.Equal(typeof(string), comments.Type); + } + + // The method under test uses an attribute on the parameter to set a 'default' source + [Fact] + public void GetApiDescription_ParameterDescription_ComplexDTO_AmbientValueProviderMetadata() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsProductChangeDTO_Query)); + var parameterDescriptor = action.Parameters.Single(); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + Assert.Equal(4, description.ParameterDescriptions.Count); + + var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id"); + Assert.Same(ApiParameterSource.Path, id.Source); + Assert.Equal(typeof(int), id.Type); + + var product = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product"); + Assert.Same(ApiParameterSource.Body, product.Source); + Assert.Equal(typeof(Product), product.Type); + + var userId = Assert.Single(description.ParameterDescriptions, p => p.Name == "UserId"); + Assert.Same(ApiParameterSource.Header, userId.Source); + Assert.Equal(typeof(string), userId.Type); + + var comments = Assert.Single(description.ParameterDescriptions, p => p.Name == "Comments"); + Assert.Same(ApiParameterSource.Query, comments.Source); + Assert.Equal(typeof(string), comments.Type); + } + + [Fact] + public void GetApiDescription_ParameterDescription_ComplexDTO_AnotherLevel() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsOrderDTO)); + var parameterDescriptor = action.Parameters.Single(); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + Assert.Equal(4, description.ParameterDescriptions.Count); + + var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id"); + Assert.Same(ApiParameterSource.Path, id.Source); + Assert.Equal(typeof(int), id.Type); + + var quantity = Assert.Single(description.ParameterDescriptions, p => p.Name == "Quantity"); + Assert.Same(ApiParameterSource.ModelBinding, quantity.Source); + Assert.Equal(typeof(int), quantity.Type); + + var productId = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product.Id"); + Assert.Same(ApiParameterSource.ModelBinding, productId.Source); + Assert.Equal(typeof(int), productId.Type); + + var price = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product.Price"); + Assert.Same(ApiParameterSource.Query, price.Source); + Assert.Equal(typeof(decimal), price.Type); + } + + // The method under test uses an attribute on the parameter to set a 'default' source + [Fact] + public void GetApiDescription_ParameterDescription_ComplexDTO_AnotherLevel_AmbientValueProviderMetadata() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsOrderDTO_Query)); + var parameterDescriptor = action.Parameters.Single(); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + Assert.Equal(3, description.ParameterDescriptions.Count); + + var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id"); + Assert.Same(ApiParameterSource.Path, id.Source); + Assert.Equal(typeof(int), id.Type); + + var quantity = Assert.Single(description.ParameterDescriptions, p => p.Name == "Quantity"); + Assert.Same(ApiParameterSource.Query, quantity.Source); + Assert.Equal(typeof(int), quantity.Type); + + var product = Assert.Single(description.ParameterDescriptions, p => p.Name == "Product"); + Assert.Same(ApiParameterSource.Query, product.Source); + Assert.Equal(typeof(OrderProductDTO), product.Type); + } + + [Fact] + public void GetApiDescription_ParameterDescription_BreaksCycles() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsCycle)); + var parameterDescriptor = action.Parameters.Single(); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var c = Assert.Single(description.ParameterDescriptions); + Assert.Same(ApiParameterSource.Query, c.Source); + Assert.Equal("C.C", c.Name); + Assert.Equal(typeof(Cycle1), c.Type); + } + + [Fact] + public void GetApiDescription_ParameterDescription_DTOWithCollection() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsHasCollection)); + var parameterDescriptor = action.Parameters.Single(); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var products = Assert.Single(description.ParameterDescriptions); + Assert.Same(ApiParameterSource.Query, products.Source); + Assert.Equal("Products", products.Name); + Assert.Equal(typeof(Product[]), products.Type); + } + + // If a property/parameter is a collection, we automatically treat it as a leaf-node. + [Fact] + public void GetApiDescription_ParameterDescription_DTOWithCollection_ElementsWithBinderMetadataIgnored() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsHasCollection_Complex)); + var parameterDescriptor = action.Parameters.Single(); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var c = Assert.Single(description.ParameterDescriptions); + Assert.Same(ApiParameterSource.ModelBinding, c.Source); + Assert.Equal("c", c.Name); + Assert.Equal(typeof(HasCollection_Complex), c.Type); + } + + [Fact] + public void GetApiDescription_ParameterDescription_RedundentMetadataMergedWithParent() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsRedundentMetadata)); + var parameterDescriptor = action.Parameters.Single(); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var r = Assert.Single(description.ParameterDescriptions); + Assert.Same(ApiParameterSource.Query, r.Source); + Assert.Equal("r", r.Name); + Assert.Equal(typeof(RedundentMetadata), r.Type); + } + + [Fact] + public void GetApiDescription_ParameterDescription_RedundentMetadata_WithParameterMetadata() + { + // Arrange + var action = CreateActionDescriptor(nameof(AcceptsPerson)); + var parameterDescriptor = action.Parameters.Single(); + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + + var name = Assert.Single(description.ParameterDescriptions, p => p.Name == "Name"); + Assert.Same(ApiParameterSource.Header, name.Source); + Assert.Equal(typeof(string), name.Type); + + var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "Id"); + Assert.Same(ApiParameterSource.Form, id.Source); + Assert.Equal(typeof(int), id.Type); + } + private IReadOnlyList GetApiDescriptions(ActionDescriptor action) { return GetApiDescriptions(action, CreateFormatters()); @@ -602,18 +913,12 @@ namespace Microsoft.AspNet.Mvc.Description constraintResolver.Setup(c => c.ResolveConstraint("int")) .Returns(new IntRouteConstraint()); - var modelMetadataProvider = new Mock(MockBehavior.Strict); - modelMetadataProvider - .Setup(mmp => mmp.GetMetadataForType(null, It.IsAny())) - .Returns((Func accessor, Type type) => - { - return new ModelMetadata(modelMetadataProvider.Object, null, accessor, type, null); - }); + var modelMetadataProvider = new DataAnnotationsModelMetadataProvider(); var provider = new DefaultApiDescriptionProvider( formattersProvider.Object, constraintResolver.Object, - modelMetadataProvider.Object); + modelMetadataProvider); provider.Invoke(context, () => { }); return context.Results; @@ -646,6 +951,18 @@ namespace Microsoft.AspNet.Mvc.Description methodName ?? "ReturnsObject", BindingFlags.Instance | BindingFlags.NonPublic); + action.Parameters = new List(); + + foreach (var parameter in action.MethodInfo.GetParameters()) + { + action.Parameters.Add(new ParameterDescriptor() + { + BinderMetadata = parameter.GetCustomAttributes().OfType().FirstOrDefault(), + Name = parameter.Name, + ParameterType = parameter.ParameterType, + }); + } + return action; } @@ -699,12 +1016,198 @@ namespace Microsoft.AspNet.Mvc.Description return null; } + private void AcceptsProduct(Product product) + { + } + + private void AcceptsProduct_Body([FromBody] Product product) + { + } + + private void AcceptsProduct_Form([FromForm] Product product) + { + } + + // This will show up as source = model binding + private void AcceptsProduct_Default([ModelBinder] Product product) + { + } + + // This will show up as source = unknown + private void AcceptsProduct_Custom([ModelBinder(BinderType = typeof(BodyModelBinder))] Product product) + { + } + + private void AcceptsId_Route([FromRoute] int id) + { + } + + private void AcceptsId_Query([FromQuery] int id) + { + } + + private void AcceptsId_Header([FromHeader] int id) + { + } + + private void AcceptsFormatters_Services([FromServices] IOutputFormattersProvider formatters) + { + } + + private void AcceptsProductChangeDTO(ProductChangeDTO dto) + { + } + + private void AcceptsProductChangeDTO_Query([FromQuery] ProductChangeDTO dto) + { + } + + private void AcceptsOrderDTO(OrderDTO dto) + { + } + + private void AcceptsOrderDTO_Query([FromQuery] OrderDTO dto) + { + } + + private void AcceptsCycle(Cycle1 c) + { + } + + private void AcceptsHasCollection(HasCollection c) + { + } + + private void AcceptsHasCollection_Complex(HasCollection_Complex c) + { + } + + private void AcceptsRedundentMetadata([FromQuery] RedundentMetadata r) + { + } + + private void AcceptsPerson([FromForm] Person person) + { + } + + private void FromRouting([FromRoute] int id) + { + } + + private void FromModelBinding(int id) + { + } + + private void FromCustom([ModelBinder(BinderType = typeof(BodyModelBinder))] int id) + { + } + + private void FromHeader([FromHeader] int id) + { + } + + private void FromBody([FromBody] int id) + { + } + private class Product { + public int ProductId { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } } private class Order { + public int OrderId { get; set; } + + public int ProductId { get; set; } + + public int Quantity { get; set; } + + public decimal Price { get; set; } + } + + private class ProductChangeDTO + { + [FromRoute] + public int Id { get; set; } + + [FromBody] + public Product Product { get; set; } + + [FromHeader] + public string UserId { get; set; } + + public string Comments { get; set; } + } + + private class OrderDTO + { + [FromRoute] + public int Id { get; set; } + + public int Quantity { get; set; } + + public OrderProductDTO Product { get; set; } + } + + private class OrderProductDTO + { + public int Id { get; set; } + + [FromQuery] + public decimal Price { get; set; } + } + + private class Cycle1 + { + public Cycle2 C { get; set; } + } + + private class Cycle2 + { + [FromQuery] + public Cycle1 C { get; set; } + } + + private class HasCollection + { + [FromQuery] + public Product[] Products { get; set; } + } + + private class HasCollection_Complex + { + public Child[] Items { get; set; } + } + + private class Child + { + [FromQuery] + public int Id { get; set; } + + public string Name { get; set; } + } + + private class RedundentMetadata + { + [FromQuery] + public int Id { get; set; } + + [FromQuery] + public string Name { get; set; } + } + + public class Person + { + [FromHeader(Name = "Name")] + public string Name { get; set; } + + [FromForm] + public int Id { get; set; } } private class MockFormatter : OutputFormatter diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs index 59ae5c105d..0e92daea96 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNet.Builder; +using Microsoft.AspNet.Mvc.Description; using Microsoft.AspNet.TestHost; using Newtonsoft.Json; using Xunit; @@ -176,9 +177,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var parameter = Assert.Single(description.ParameterDescriptions); Assert.Equal("id", parameter.Name); - Assert.False(parameter.IsOptional); + Assert.False(parameter.RouteInfo.IsOptional); Assert.Equal("Path", parameter.Source); - Assert.Empty(parameter.ConstraintTypes); + Assert.Empty(parameter.RouteInfo.ConstraintTypes); } [Fact] @@ -201,9 +202,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var parameter = Assert.Single(description.ParameterDescriptions); Assert.Equal("integer", parameter.Name); - Assert.False(parameter.IsOptional); + Assert.False(parameter.RouteInfo.IsOptional); Assert.Equal("Path", parameter.Source); - Assert.Equal("IntRouteConstraint", Assert.Single(parameter.ConstraintTypes)); + Assert.Equal("IntRouteConstraint", Assert.Single(parameter.RouteInfo.ConstraintTypes)); } [Fact] @@ -226,7 +227,7 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var parameter = Assert.Single(description.ParameterDescriptions); Assert.Equal("parameter", parameter.Name); - Assert.False(parameter.IsOptional); + Assert.False(parameter.RouteInfo.IsOptional); Assert.Equal("Path", parameter.Source); } @@ -252,9 +253,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests var parameter = Assert.Single(description.ParameterDescriptions); Assert.Equal("integer", parameter.Name); - Assert.False(parameter.IsOptional); + Assert.False(parameter.RouteInfo.IsOptional); Assert.Equal("Path", parameter.Source); - Assert.Equal("IntRouteConstraint", Assert.Single(parameter.ConstraintTypes)); + Assert.Equal("IntRouteConstraint", Assert.Single(parameter.RouteInfo.ConstraintTypes)); } [Fact] @@ -281,19 +282,19 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(expectedRelativePath, description.RelativePath); var month = Assert.Single(description.ParameterDescriptions, p => p.Name == "month"); - Assert.False(month.IsOptional); + Assert.False(month.RouteInfo.IsOptional); Assert.Equal("Path", month.Source); - Assert.Equal("RangeRouteConstraint", Assert.Single(month.ConstraintTypes)); + Assert.Equal("RangeRouteConstraint", Assert.Single(month.RouteInfo.ConstraintTypes)); var day = Assert.Single(description.ParameterDescriptions, p => p.Name == "day"); - Assert.False(day.IsOptional); + Assert.False(day.RouteInfo.IsOptional); Assert.Equal("Path", day.Source); - Assert.Equal("IntRouteConstraint", Assert.Single(day.ConstraintTypes)); + Assert.Equal("IntRouteConstraint", Assert.Single(day.RouteInfo.ConstraintTypes)); var year = Assert.Single(description.ParameterDescriptions, p => p.Name == "year"); - Assert.False(year.IsOptional); + Assert.False(year.RouteInfo.IsOptional); Assert.Equal("Path", year.Source); - Assert.Equal("IntRouteConstraint", Assert.Single(year.ConstraintTypes)); + Assert.Equal("IntRouteConstraint", Assert.Single(year.RouteInfo.ConstraintTypes)); } [Fact] @@ -319,19 +320,19 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(expectedRelativePath, description.RelativePath); var month = Assert.Single(description.ParameterDescriptions, p => p.Name == "month"); - Assert.False(month.IsOptional); + Assert.False(month.RouteInfo.IsOptional); Assert.Equal("Path", month.Source); - Assert.Equal("RangeRouteConstraint", Assert.Single(month.ConstraintTypes)); + Assert.Equal("RangeRouteConstraint", Assert.Single(month.RouteInfo.ConstraintTypes)); var day = Assert.Single(description.ParameterDescriptions, p => p.Name == "day"); - Assert.True(day.IsOptional); - Assert.Equal("Path", day.Source); - Assert.Equal("IntRouteConstraint", Assert.Single(day.ConstraintTypes)); + Assert.True(day.RouteInfo.IsOptional); + Assert.Equal("ModelBinding", day.Source); + Assert.Equal("IntRouteConstraint", Assert.Single(day.RouteInfo.ConstraintTypes)); var year = Assert.Single(description.ParameterDescriptions, p => p.Name == "year"); - Assert.True(year.IsOptional); - Assert.Equal("Path", year.Source); - Assert.Equal("IntRouteConstraint", Assert.Single(year.ConstraintTypes)); + Assert.True(year.RouteInfo.IsOptional); + Assert.Equal("ModelBinding", year.Source); + Assert.Equal("IntRouteConstraint", Assert.Single(year.RouteInfo.ConstraintTypes)); } [Fact] @@ -383,8 +384,8 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal("ApiExplorerRouteAndPathParametersInformation/Optional/{id}", description.RelativePath); var id = Assert.Single(description.ParameterDescriptions, p => p.Name == "id"); - Assert.True(id.IsOptional); - Assert.Equal("Path", id.Source); + Assert.True(id.RouteInfo.IsOptional); + Assert.Equal("ModelBinding", id.Source); } [Fact] @@ -722,6 +723,158 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(formatterType, format.FormatterType); } + [Fact] + public async Task ApiExplorer_Parameters_SimpleTypes_Default() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/ApiExplorerParameters/SimpleParameters"); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(body); + + // Assert + var description = Assert.Single(result); + var parameters = description.ParameterDescriptions; + + Assert.Equal(2, parameters.Count); + + var i = Assert.Single(parameters, p => p.Name == "i"); + Assert.Equal(ApiParameterSource.ModelBinding.Id, i.Source); + Assert.Equal(typeof(int).FullName, i.Type); + + var s = Assert.Single(parameters, p => p.Name == "s"); + Assert.Equal(ApiParameterSource.ModelBinding.Id, s.Source); + Assert.Equal(typeof(string).FullName, s.Type); + } + + [Fact] + public async Task ApiExplorer_Parameters_SimpleTypes_BinderMetadataOnParameters() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/ApiExplorerParameters/SimpleParametersWithBinderMetadata"); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(body); + + // Assert + var description = Assert.Single(result); + var parameters = description.ParameterDescriptions; + + Assert.Equal(2, parameters.Count); + + var i = Assert.Single(parameters, p => p.Name == "i"); + Assert.Equal(ApiParameterSource.Query.Id, i.Source); + Assert.Equal(typeof(int).FullName, i.Type); + + var s = Assert.Single(parameters, p => p.Name == "s"); + Assert.Equal(ApiParameterSource.Path.Id, s.Source); + Assert.Equal(typeof(string).FullName, s.Type); + } + + [Fact] + public async Task ApiExplorer_ParametersSimpleModel() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/ApiExplorerParameters/SimpleModel"); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(body); + + // Assert + var description = Assert.Single(result); + var parameters = description.ParameterDescriptions; + + Assert.Equal(1, parameters.Count); + + var product = Assert.Single(parameters, p => p.Name == "product"); + Assert.Equal(ApiParameterSource.ModelBinding.Id, product.Source); + Assert.Equal(typeof(ApiExplorerWebSite.Product).FullName, product.Type); + } + + [Fact] + public async Task ApiExplorer_Parameters_SimpleTypes_SimpleModel_FromBody() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/ApiExplorerParameters/SimpleModelFromBody/5"); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(body); + + // Assert + var description = Assert.Single(result); + var parameters = description.ParameterDescriptions; + + Assert.Equal(2, parameters.Count); + + var id = Assert.Single(parameters, p => p.Name == "id"); + Assert.Equal(ApiParameterSource.Path.Id, id.Source); + Assert.Equal(typeof(int).FullName, id.Type); + + var product = Assert.Single(parameters, p => p.Name == "product"); + Assert.Equal(ApiParameterSource.Body.Id, product.Source); + Assert.Equal(typeof(ApiExplorerWebSite.Product).FullName, product.Type); + } + + [Fact] + public async Task ApiExplorer_Parameters_SimpleTypes_ComplexModel() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var response = await client.GetAsync("http://localhost/ApiExplorerParameters/ComplexModel"); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(body); + + // Assert + var description = Assert.Single(result); + var parameters = description.ParameterDescriptions; + + Assert.Equal(6, parameters.Count); + + var customerId = Assert.Single(parameters, p => p.Name == "CustomerId"); + Assert.Equal(ApiParameterSource.Query.Id, customerId.Source); + Assert.Equal(typeof(string).FullName, customerId.Type); + + var referrer = Assert.Single(parameters, p => p.Name == "Referrer"); + Assert.Equal(ApiParameterSource.Header.Id, referrer.Source); + Assert.Equal(typeof(string).FullName, referrer.Type); + + var quantity = Assert.Single(parameters, p => p.Name == "Details.Quantity"); + Assert.Equal(ApiParameterSource.Form.Id, quantity.Source); + Assert.Equal(typeof(int).FullName, quantity.Type); + + var product = Assert.Single(parameters, p => p.Name == "Details.Product"); + Assert.Equal(ApiParameterSource.Form.Id, product.Source); + Assert.Equal(typeof(ApiExplorerWebSite.Product).FullName, product.Type); + + var shippingInstructions = Assert.Single(parameters, p => p.Name == "Comments.ShippingInstructions"); + Assert.Equal(ApiParameterSource.Query.Id, shippingInstructions.Source); + Assert.Equal(typeof(string).FullName, shippingInstructions.Type); + + var feedback = Assert.Single(parameters, p => p.Name == "Comments.Feedback"); + Assert.Equal(ApiParameterSource.Form.Id, feedback.Source); + Assert.Equal(typeof(string).FullName, feedback.Type); + } + // Used to serialize data between client and server private class ApiExplorerData { @@ -741,15 +894,23 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests // Used to serialize data between client and server private class ApiExplorerParameterData { - public bool IsOptional { get; set; } - public string Name { get; set; } + public ApiExplorerParameterRouteInfo RouteInfo { get; set; } + public string Source { get; set; } public string Type { get; set; } + } + // Used to serialize data between client and server + private class ApiExplorerParameterRouteInfo + { public string[] ConstraintTypes { get; set; } + + public object DefaultValue { get; set; } + + public bool IsOptional { get; set; } } // Used to serialize data between client and server diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs index cbce75127a..06668c7401 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs +++ b/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Metadata/ModelMetadataTest.cs @@ -2,7 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; using System.Linq; #if ASPNET50 using Moq; @@ -84,6 +87,7 @@ namespace Microsoft.AspNet.Mvc.ModelBinding Assert.False(metadata.HideSurroundingHtml); Assert.True(metadata.HtmlEncode); Assert.False(metadata.IsComplexType); + Assert.False(metadata.IsCollectionType); Assert.False(metadata.IsNullableValueType); Assert.False(metadata.IsReadOnly); Assert.False(metadata.IsRequired); @@ -128,7 +132,12 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var provider = new EmptyModelMetadataProvider(); // Act - var modelMetadata = new ModelMetadata(provider, null, null, type, null); + var modelMetadata = new ModelMetadata( + provider, + containerType: null, + modelAccessor: null, + modelType: type, + propertyName: null); // Assert Assert.False(modelMetadata.IsComplexType); @@ -145,12 +154,72 @@ namespace Microsoft.AspNet.Mvc.ModelBinding var provider = new EmptyModelMetadataProvider(); // Act - var modelMetadata = new ModelMetadata(provider, null, null, type, null); + var modelMetadata = new ModelMetadata( + provider, + containerType: null, + modelAccessor: null, + modelType: type, + propertyName: null); // Assert Assert.True(modelMetadata.IsComplexType); } + [Theory] + [InlineData(typeof(object))] + [InlineData(typeof(int))] + [InlineData(typeof(NonCollectionType))] + [InlineData(typeof(string))] + public void IsCollectionType_NonCollectionTypes(Type type) + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + + // Act + var modelMetadata = new ModelMetadata( + provider, + containerType: null, + modelAccessor: null, + modelType: type, + propertyName: null); + + // Assert + Assert.False(modelMetadata.IsCollectionType); + } + + [Theory] + [InlineData(typeof(int[]))] + [InlineData(typeof(List))] + [InlineData(typeof(DerivedList))] + [InlineData(typeof(IEnumerable))] + [InlineData(typeof(IEnumerable))] + [InlineData(typeof(Collection))] + [InlineData(typeof(Dictionary))] + public void IsCollectionType_CollectionTypes(Type type) + { + // Arrange + var provider = new EmptyModelMetadataProvider(); + + // Act + var modelMetadata = new ModelMetadata( + provider, + containerType: null, + modelAccessor: null, + modelType: type, + propertyName: null); + + // Assert + Assert.True(modelMetadata.IsCollectionType); + } + + private class NonCollectionType + { + } + + private class DerivedList : List + { + } + // IsNullableValueType [Fact] diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs index a00f14c7f9..b412d3c881 100644 --- a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs +++ b/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; using Microsoft.AspNet.Mvc; @@ -9,11 +10,11 @@ using Microsoft.AspNet.Mvc.Description; namespace ApiExplorerWebSite { /// - /// An action filter that looks up and serializes Api Explorer data for the action. + /// A resource filter that looks up and serializes Api Explorer data for the action. /// /// This replaces the 'actual' output of the action. /// - public class ApiExplorerDataFilter : ActionFilterAttribute + public class ApiExplorerDataFilter : IResourceFilter { private readonly IApiDescriptionGroupCollectionProvider _descriptionProvider; @@ -22,7 +23,7 @@ namespace ApiExplorerWebSite _descriptionProvider = descriptionProvider; } - public override void OnActionExecuted(ActionExecutedContext context) + public void OnResourceExecuting(ResourceExecutingContext context) { var descriptions = new List(); foreach (var group in _descriptionProvider.ApiDescriptionGroups.Items) @@ -39,6 +40,11 @@ namespace ApiExplorerWebSite context.Result = new JsonResult(descriptions); } + public void OnResourceExecuted(ResourceExecutedContext context) + { + throw new NotImplementedException(); + } + private ApiExplorerData CreateSerializableData(ApiDescription description) { var data = new ApiExplorerData() @@ -53,13 +59,21 @@ namespace ApiExplorerWebSite { var parameterData = new ApiExplorerParameterData() { - IsOptional = parameter.IsOptional, Name = parameter.Name, - Source = parameter.Source.ToString(), - Type = parameter?.Type?.FullName, - ConstraintTypes = parameter?.Constraints?.Select(c => c.GetType().Name).ToArray(), + Source = parameter.Source.Id, + Type = parameter.Type?.FullName, }; + if (parameter.RouteInfo != null) + { + parameterData.RouteInfo = new ApiExplorerParameterRouteInfo() + { + ConstraintTypes = parameter.RouteInfo.Constraints?.Select(c => c.GetType().Name).ToArray(), + DefaultValue = parameter.RouteInfo.DefaultValue, + IsOptional = parameter.RouteInfo.IsOptional, + }; + } + data.ParameterDescriptions.Add(parameterData); } @@ -96,15 +110,23 @@ namespace ApiExplorerWebSite // Used to serialize data between client and server private class ApiExplorerParameterData { - public bool IsOptional { get; set; } - public string Name { get; set; } + public ApiExplorerParameterRouteInfo RouteInfo { get; set; } + public string Source { get; set; } public string Type { get; set; } + } + // Used to serialize data between client and server + private class ApiExplorerParameterRouteInfo + { public string[] ConstraintTypes { get; set; } + + public object DefaultValue { get; set; } + + public bool IsOptional { get; set; } } // Used to serialize data between client and server diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs new file mode 100644 index 0000000000..d55ad41438 --- /dev/null +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs @@ -0,0 +1,32 @@ +// 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 ApiExplorerWebSite.Controllers +{ + [Route("ApiExplorerParameters/[action]")] + public class ApiExplorerParametersController : Controller + { + public void SimpleParameters(int i, string s) + { + } + + public void SimpleParametersWithBinderMetadata([FromQuery] int i, [FromRoute] string s) + { + } + + public void SimpleModel(Product product) + { + } + + [Route("{id}")] + public void SimpleModelFromBody(int id, [FromBody] Product product) + { + } + + public void ComplexModel([FromQuery] OrderDTO order) + { + } + } +} \ No newline at end of file diff --git a/test/WebSites/ApiExplorerWebSite/Models/CustomerCommentsDTO.cs b/test/WebSites/ApiExplorerWebSite/Models/CustomerCommentsDTO.cs new file mode 100644 index 0000000000..af89c259e9 --- /dev/null +++ b/test/WebSites/ApiExplorerWebSite/Models/CustomerCommentsDTO.cs @@ -0,0 +1,15 @@ +// 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 ApiExplorerWebSite +{ + public class CustomerCommentsDTO + { + [FromQuery] + public string ShippingInstructions { get; set; } + + public string Feedback { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/ApiExplorerWebSite/Models/IOrderRepository.cs b/test/WebSites/ApiExplorerWebSite/Models/IOrderRepository.cs new file mode 100644 index 0000000000..a51b4cafa5 --- /dev/null +++ b/test/WebSites/ApiExplorerWebSite/Models/IOrderRepository.cs @@ -0,0 +1,9 @@ +// 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 ApiExplorerWebSite +{ + public interface IOrderRepository + { + } +} \ No newline at end of file diff --git a/test/WebSites/ApiExplorerWebSite/Models/OrderDTO.cs b/test/WebSites/ApiExplorerWebSite/Models/OrderDTO.cs new file mode 100644 index 0000000000..51e0c2d33b --- /dev/null +++ b/test/WebSites/ApiExplorerWebSite/Models/OrderDTO.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 Microsoft.AspNet.Mvc; + +namespace ApiExplorerWebSite +{ + public class OrderDTO + { + [FromServices] + public IOrderRepository Repository { get; set; } + + public string CustomerId { get; set; } + + [FromHeader(Name = "Referrer")] + public string ReferrerId { get; set; } + + public OrderDetailsDTO Details { get; set; } + + [FromForm] + public CustomerCommentsDTO Comments { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/ApiExplorerWebSite/Models/OrderDetailsDTO.cs b/test/WebSites/ApiExplorerWebSite/Models/OrderDetailsDTO.cs new file mode 100644 index 0000000000..cd4d0a6a55 --- /dev/null +++ b/test/WebSites/ApiExplorerWebSite/Models/OrderDetailsDTO.cs @@ -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 Microsoft.AspNet.Mvc; + +namespace ApiExplorerWebSite +{ + public class OrderDetailsDTO + { + [FromForm] + public int Quantity { get; set; } + + [FromForm] + public Product Product { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/ApiExplorerWebSite/Models/Product.cs b/test/WebSites/ApiExplorerWebSite/Models/Product.cs index 2d608bedd2..c5ae342045 100644 --- a/test/WebSites/ApiExplorerWebSite/Models/Product.cs +++ b/test/WebSites/ApiExplorerWebSite/Models/Product.cs @@ -5,5 +5,8 @@ namespace ApiExplorerWebSite { public class Product { + public int Id { get; set; } + + public string Name { get; set; } } } \ No newline at end of file From 14bd7dcd5ea57badcf37f02e2e5c035103b55d79 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Thu, 22 Jan 2015 12:30:10 -0800 Subject: [PATCH 115/118] Handle trailing semicolon after @inject. - Made @inject handle trailing semicolons identical to @using; essentially ignores it. - Added parser, runtime/designtime codegen and functional tests. - Added Microsoft.AspNet.Mvc.Common.Test. - Transitioned pre-existing Microsoft.AspNet.Mvc.Common tests to the new test project. - Updated transitioned tests to also work in CoreCLR (except ones with moq). #1857 --- Mvc.sln | 17 +++- .../Properties/AssemblyInfo.cs | 7 ++ .../StringHelper.cs | 58 +++++++++++ .../MvcRazorCodeParser.cs | 5 +- .../CopyOnWriteDictionaryTest.cs | 6 +- .../Microsoft.AspNet.Mvc.Common.Test.kproj | 17 ++++ .../PropertyHelperTest.cs | 10 +- .../StringHelperTest.cs | 97 +++++++++++++++++++ .../TaskHelperTest.cs | 2 +- .../TypeExtensionsTest.cs} | 2 +- .../project.json | 21 ++++ .../MvcCSharpRazorCodeParserTest.cs | 44 +++++++++ .../MvcRazorHostTest.cs | 23 +++++ .../Input/InjectWithSemicolon.cshtml | 5 + .../Output/DesignTime/InjectWithSemicolon.cs | 69 +++++++++++++ .../Output/Runtime/InjectWithSemicolon.cs | 45 +++++++++ 16 files changed, 417 insertions(+), 11 deletions(-) create mode 100644 src/Microsoft.AspNet.Mvc.Common/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.AspNet.Mvc.Common/StringHelper.cs rename test/{Microsoft.AspNet.Mvc.Core.Test => Microsoft.AspNet.Mvc.Common.Test}/CopyOnWriteDictionaryTest.cs (98%) create mode 100644 test/Microsoft.AspNet.Mvc.Common.Test/Microsoft.AspNet.Mvc.Common.Test.kproj rename test/{Microsoft.AspNet.Mvc.Core.Test => Microsoft.AspNet.Mvc.Common.Test}/PropertyHelperTest.cs (96%) create mode 100644 test/Microsoft.AspNet.Mvc.Common.Test/StringHelperTest.cs rename test/{Microsoft.AspNet.Mvc.Core.Test/Internal => Microsoft.AspNet.Mvc.Common.Test}/TaskHelperTest.cs (97%) rename test/{Microsoft.AspNet.Mvc.ModelBinding.Test/Internal/TypeExtensionTests.cs => Microsoft.AspNet.Mvc.Common.Test/TypeExtensionsTest.cs} (98%) create mode 100644 test/Microsoft.AspNet.Mvc.Common.Test/project.json create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/InjectWithSemicolon.cshtml create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.cs create mode 100644 test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/InjectWithSemicolon.cs diff --git a/Mvc.sln b/Mvc.sln index 01b51cf2ce..4ff2f92df8 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.22303.1 +VisualStudioVersion = 14.0.22512.0 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{DAAE4C74-D06F-4874-A166-33305D2643CE}" EndProject @@ -122,6 +122,8 @@ Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "CustomRouteWebSite", "test\ EndProject Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "ResponseCacheWebSite", "test\WebSites\ResponseCacheWebSite\ResponseCacheWebSite.kproj", "{BDEEBE09-C0C4-433C-B0B8-8478C9776996}" EndProject +Project("{8BB2217D-0F2D-49D1-97BC-3654ED321F3B}") = "Microsoft.AspNet.Mvc.Common.Test", "test\Microsoft.AspNet.Mvc.Common.Test\Microsoft.AspNet.Mvc.Common.Test.kproj", "{0449D6D2-BE1B-4E29-8E1B-444420802C03}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -682,6 +684,18 @@ Global {BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|Mixed Platforms.Build.0 = Release|Any CPU {BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|x86.ActiveCfg = Release|Any CPU {BDEEBE09-C0C4-433C-B0B8-8478C9776996}.Release|x86.Build.0 = Release|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|x86.ActiveCfg = Debug|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Debug|x86.Build.0 = Debug|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|Any CPU.Build.0 = Release|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|x86.ActiveCfg = Release|Any CPU + {0449D6D2-BE1B-4E29-8E1B-444420802C03}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -741,5 +755,6 @@ Global {AF210F69-9D31-43AF-AC3A-CD366E252218} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {364EC3C6-C9DB-45E0-A0F2-1EE61E4B429B} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {BDEEBE09-C0C4-433C-B0B8-8478C9776996} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {0449D6D2-BE1B-4E29-8E1B-444420802C03} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} EndGlobalSection EndGlobal diff --git a/src/Microsoft.AspNet.Mvc.Common/Properties/AssemblyInfo.cs b/src/Microsoft.AspNet.Mvc.Common/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..1f3fdbe409 --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Common/Properties/AssemblyInfo.cs @@ -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")] diff --git a/src/Microsoft.AspNet.Mvc.Common/StringHelper.cs b/src/Microsoft.AspNet.Mvc.Common/StringHelper.cs new file mode 100644 index 0000000000..8f3333692b --- /dev/null +++ b/src/Microsoft.AspNet.Mvc.Common/StringHelper.cs @@ -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); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorCodeParser.cs b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorCodeParser.cs index ff40c3b42f..84be0bc674 100644 --- a/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorCodeParser.cs +++ b/src/Microsoft.AspNet.Mvc.Razor.Host/MvcRazorCodeParser.cs @@ -127,8 +127,9 @@ namespace Microsoft.AspNet.Mvc.Razor .Value .Substring(typeName.Length); - Span.CodeGenerator = new InjectParameterGenerator(typeName.Trim(), - propertyName.Trim()); + // ';' is optional + propertyName = StringHelper.TrimSpacesAndChars(propertyName, ';'); + Span.CodeGenerator = new InjectParameterGenerator(typeName.Trim(), propertyName); // Output the span and finish the block CompleteBlock(); diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/CopyOnWriteDictionaryTest.cs b/test/Microsoft.AspNet.Mvc.Common.Test/CopyOnWriteDictionaryTest.cs similarity index 98% rename from test/Microsoft.AspNet.Mvc.Core.Test/CopyOnWriteDictionaryTest.cs rename to test/Microsoft.AspNet.Mvc.Common.Test/CopyOnWriteDictionaryTest.cs index f1d7263235..ba6edf507f 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/CopyOnWriteDictionaryTest.cs +++ b/test/Microsoft.AspNet.Mvc.Common.Test/CopyOnWriteDictionaryTest.cs @@ -3,13 +3,16 @@ using System; using System.Collections.Generic; +#if !ASPNETCORE50 using Moq; +#endif using Xunit; -namespace Microsoft.AspNet.Mvc.Core +namespace Microsoft.AspNet.Mvc { public class CopyOnWriteDictionaryTest { +#if !ASPNETCORE50 [Fact] public void ReadOperation_DelegatesToSourceDictionary_IfNoMutationsArePerformed() { @@ -49,6 +52,7 @@ namespace Microsoft.AspNet.Mvc.Core Assert.False(copyOnWriteDictionary.TryGetValue("different-key", out value)); sourceDictionary.Verify(); } +#endif [Fact] public void ReadOperation_DoesNotDelegateToSourceDictionary_OnceAValueIsChanged() diff --git a/test/Microsoft.AspNet.Mvc.Common.Test/Microsoft.AspNet.Mvc.Common.Test.kproj b/test/Microsoft.AspNet.Mvc.Common.Test/Microsoft.AspNet.Mvc.Common.Test.kproj new file mode 100644 index 0000000000..fdf5b55d84 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Common.Test/Microsoft.AspNet.Mvc.Common.Test.kproj @@ -0,0 +1,17 @@ + + + + 14.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + 0449d6d2-be1b-4e29-8e1b-444420802c03 + ..\..\artifacts\obj\$(MSBuildProjectName) + ..\..\artifacts\bin\$(MSBuildProjectName)\ + + + 2.0 + + + \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/PropertyHelperTest.cs b/test/Microsoft.AspNet.Mvc.Common.Test/PropertyHelperTest.cs similarity index 96% rename from test/Microsoft.AspNet.Mvc.Core.Test/PropertyHelperTest.cs rename to test/Microsoft.AspNet.Mvc.Common.Test/PropertyHelperTest.cs index d52bc1e54d..3c47b7a3b3 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/PropertyHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Common.Test/PropertyHelperTest.cs @@ -16,10 +16,10 @@ namespace Microsoft.AspNet.Mvc { // Arrange var anonymous = new { foo = "bar" }; - PropertyInfo property = anonymous.GetType().GetProperties().First(); + var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; // Act - PropertyHelper helper = new PropertyHelper(property); + var helper = new PropertyHelper(property); // Assert Assert.Equal("foo", property.Name); @@ -31,10 +31,10 @@ namespace Microsoft.AspNet.Mvc { // Arrange var anonymous = new { bar = "baz" }; - PropertyInfo property = anonymous.GetType().GetProperties().First(); + var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; // Act - PropertyHelper helper = new PropertyHelper(property); + var helper = new PropertyHelper(property); // Assert Assert.Equal("bar", helper.Name); @@ -46,7 +46,7 @@ namespace Microsoft.AspNet.Mvc { // Arrange var anonymous = new { foo = 32 }; - var property = anonymous.GetType().GetProperties().First(); + var property = PropertyHelper.GetProperties(anonymous.GetType()).First().Property; // Act var helper = new PropertyHelper(property); diff --git a/test/Microsoft.AspNet.Mvc.Common.Test/StringHelperTest.cs b/test/Microsoft.AspNet.Mvc.Common.Test/StringHelperTest.cs new file mode 100644 index 0000000000..9657f99606 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Common.Test/StringHelperTest.cs @@ -0,0 +1,97 @@ +// 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 Xunit; + +namespace Microsoft.AspNet.Mvc +{ + public class StringHelperTest + { + public static TheoryData TrimSpacesAndCharsData + { + get + { + // input, trimCharacters, expectedOutput + return new TheoryData + { + { "abcd", new char[] { }, "abcd" }, + { " /.", new char[] { '/', '.' }, string.Empty }, + { string.Empty, new char[] { }, string.Empty }, + { " ", new char[] { }, string.Empty }, + { " ", new char[] { }, string.Empty }, + { " / ", new char[] { '/' }, string.Empty }, + { " \t ", new char[] { '/' }, string.Empty }, + { " ", new char[] { '/' }, string.Empty }, + { " ", new char[] { '/' }, string.Empty }, + { "/", new char[] { '/' }, string.Empty }, + { "//", new char[] { '/' }, string.Empty }, + { "// ", new char[] { '/' }, string.Empty }, + { "/ ", new char[] { '/' }, string.Empty }, + { " a ", new char[] { }, "a" }, + { " a", new char[] { }, "a" }, + { "a ", new char[] { }, "a" }, + { " a ", new char[] { }, "a" }, + { " a \n\r", new char[] { }, "a" }, + { "\t\r a ", new char[] { }, "a" }, + { "\ta ", new char[] { }, "a" }, + { " a a ", new char[] { }, "a a" }, + { " a ", new char[] { '/' }, "a" }, + { " a", new char[] { '/' }, "a" }, + { "a ", new char[] { '/' }, "a" }, + { " a ", new char[] { '/' }, "a" }, + { " a \n\r", new char[] { '/' }, "a" }, + { "\t\r a ", new char[] { '/' }, "a" }, + { "\ta ", new char[] { '/' }, "a" }, + { " a a ", new char[] { '/' }, "a a" }, + { " a ", new char[] { '/', ' ' }, "a" }, + { " a", new char[] { '/', ' ' }, "a" }, + { "a ", new char[] { '/', ' ' }, "a" }, + { " a ", new char[] { '/', ' ' }, "a" }, + { " a \n\r", new char[] { '/', ' ' }, "a" }, + { "\t\r a ", new char[] { '/', ' ' }, "a" }, + { "\ta ", new char[] { '/', ' ' }, "a" }, + { " a a ", new char[] { '/', ' ' }, "a a" }, + { "/ a ", new char[] { '/' }, "a" }, + { " / a", new char[] { '/' }, "a" }, + { "a / /", new char[] { '/' }, "a" }, + { " a // //", new char[] { '/' }, "a" }, + { " a \n\r//", new char[] { '/' }, "a" }, + { "////\t\r a ", new char[] { '/' }, "a" }, + { "\ta /", new char[] { '/' }, "a" }, + { " a/ a ", new char[] { '/' }, "a/ a" }, + { "/ a ", new char[] { '/', ' ' }, "a" }, + { " / a", new char[] { '/', ' ' }, "a" }, + { "a / /", new char[] { '/', ' ' }, "a" }, + { " a // //", new char[] { '/', ' ' }, "a" }, + { " a \n\r//", new char[] { '/', ' ' }, "a" }, + { "////\t\r a ", new char[] { '/', ' ' }, "a" }, + { "\ta /", new char[] { '/', ' ' }, "a" }, + { " a/ a ", new char[] { '/', ' ' }, "a/ a" }, + { " a /.", new char[] { '/', '.' }, "a" }, + { " a", new char[] { '/', '.' }, "a" }, + { "/. ./a ", new char[] { '/', '.' }, "a" }, + { " a ", new char[] { '/', '.' }, "a" }, + { " a \n\r", new char[] { '/', '.' }, "a" }, + { "\t\r a ", new char[] { '/', '.' }, "a" }, + { "\ta ", new char[] { '/', '.' }, "a" }, + { "///..a/./a /. ./....", new char[] { '/', '.' }, "a/./a" }, + }; + } + } + + [Theory] + [MemberData(nameof(TrimSpacesAndCharsData))] + public void TrimSpacesAndChars_GeneratesExpectedOutput( + string input, + char[] trimCharacters, + string expectedOutput) + { + // Arrange & Act + var output = StringHelper.TrimSpacesAndChars(input, trimCharacters); + + // Assert + Assert.Equal(expectedOutput, output, StringComparer.Ordinal); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Internal/TaskHelperTest.cs b/test/Microsoft.AspNet.Mvc.Common.Test/TaskHelperTest.cs similarity index 97% rename from test/Microsoft.AspNet.Mvc.Core.Test/Internal/TaskHelperTest.cs rename to test/Microsoft.AspNet.Mvc.Common.Test/TaskHelperTest.cs index 346d3dd8e7..d3d3b752a5 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/Internal/TaskHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Common.Test/TaskHelperTest.cs @@ -5,7 +5,7 @@ using System; using System.Threading.Tasks; using Xunit; -namespace Microsoft.AspNet.Mvc.Internal +namespace Microsoft.AspNet.Mvc { public class TaskHelperTest { diff --git a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Internal/TypeExtensionTests.cs b/test/Microsoft.AspNet.Mvc.Common.Test/TypeExtensionsTest.cs similarity index 98% rename from test/Microsoft.AspNet.Mvc.ModelBinding.Test/Internal/TypeExtensionTests.cs rename to test/Microsoft.AspNet.Mvc.Common.Test/TypeExtensionsTest.cs index fd366867ed..ab81f6bf8c 100644 --- a/test/Microsoft.AspNet.Mvc.ModelBinding.Test/Internal/TypeExtensionTests.cs +++ b/test/Microsoft.AspNet.Mvc.Common.Test/TypeExtensionsTest.cs @@ -7,7 +7,7 @@ using Xunit; namespace Microsoft.AspNet.Mvc { - public class TypeExtensionTests + public class TypeExtensionsTest { [Theory] [InlineData(typeof(decimal))] diff --git a/test/Microsoft.AspNet.Mvc.Common.Test/project.json b/test/Microsoft.AspNet.Mvc.Common.Test/project.json new file mode 100644 index 0000000000..8e005b0732 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Common.Test/project.json @@ -0,0 +1,21 @@ +{ + "compilationOptions": { + "warningsAsErrors": "true" + }, + "dependencies": { + "Microsoft.AspNet.Mvc.Common": "6.0.0-*", + "xunit.runner.kre": "1.0.0-*", + "Microsoft.AspNet.Testing": "1.0.0-*" + }, + "commands": { + "test": "xunit.runner.kre" + }, + "frameworks": { + "aspnet50": { + "dependencies": { + "Moq": "4.2.1312.1622" + } + }, + "aspnetcore50": { } + } +} diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcCSharpRazorCodeParserTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcCSharpRazorCodeParserTest.cs index b1182cae34..7697e2cc56 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcCSharpRazorCodeParserTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcCSharpRazorCodeParserTest.cs @@ -272,6 +272,50 @@ namespace Microsoft.AspNet.Mvc.Razor Assert.Empty(errors); } + [Theory] + [InlineData("IMyService Service;", "IMyService", "Service")] + [InlineData("IMyService Service;;", "IMyService", "Service")] + [InlineData(" Microsoft.AspNet.Mvc.IHtmlHelper MyHelper; ", + "Microsoft.AspNet.Mvc.IHtmlHelper", "MyHelper")] + [InlineData(" Microsoft.AspNet.Mvc.IHtmlHelper MyHelper; ; ", + "Microsoft.AspNet.Mvc.IHtmlHelper", "MyHelper")] + [InlineData(" TestService @class; ; ", "TestService", "@class")] + [InlineData("IMyService Service ;", "IMyService", "Service")] + [InlineData("IMyService Service ; ;", "IMyService", "Service")] + [InlineData(" Microsoft.AspNet.Mvc.IHtmlHelper MyHelper ; ", + "Microsoft.AspNet.Mvc.IHtmlHelper", "MyHelper")] + [InlineData(" Microsoft.AspNet.Mvc.IHtmlHelper MyHelper ; ; ", + "Microsoft.AspNet.Mvc.IHtmlHelper", "MyHelper")] + [InlineData(" TestService @class ; ", "TestService", "@class")] + [InlineData(" TestService @class ; ; ", "TestService", "@class")] + public void ParseInjectKeyword_AllowsOptionalTrailingSemicolon( + string injectStatement, + string expectedService, + string expectedPropertyName) + { + // Arrange + var documentContent = "@inject " + injectStatement; + var factory = SpanFactory.CreateCsHtml(); + var errors = new List(); + var expectedSpans = new Span[] + { + factory.EmptyHtml(), + factory.CodeTransition(SyntaxConstants.TransitionString) + .Accepts(AcceptedCharacters.None), + factory.MetaCode("inject ") + .Accepts(AcceptedCharacters.None), + factory.Code(injectStatement) + .As(new InjectParameterGenerator(expectedService, expectedPropertyName)) + }; + + // Act + var spans = ParseDocument(documentContent, errors); + + // Assert + Assert.Equal(expectedSpans, spans); + Assert.Empty(errors); + } + [Theory] [InlineData("IMyService Service ", "IMyService", "Service")] [InlineData(" TestService @namespace ", "TestService", "@namespace")] diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs index 792c50083e..ef6972ceb4 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/MvcRazorHostTest.cs @@ -79,6 +79,7 @@ namespace Microsoft.AspNet.Mvc.Razor [InlineData("Basic")] [InlineData("Inject")] [InlineData("InjectWithModel")] + [InlineData("InjectWithSemicolon")] [InlineData("Model")] [InlineData("ModelExpressionTagHelper")] public void MvcRazorHost_ParsesAndGeneratesCodeForBasicScenarios(string scenarioName) @@ -129,6 +130,28 @@ namespace Microsoft.AspNet.Mvc.Razor RunDesignTimeTest(host, "InjectWithModel", expectedLineMappings); } + [Fact] + public void InjectVisitorWithSemicolon_GeneratesCorrectLineMappings() + { + // Arrange + var host = new MvcRazorHost(new TestFileProvider()) + { + DesignTimeMode = true + }; + host.NamespaceImports.Clear(); + var expectedLineMappings = new[] + { + BuildLineMapping(7, 0, 7, 222, 6, 7, 7), + BuildLineMapping(24, 1, 8, 729, 26, 8, 20), + BuildLineMapping(58, 2, 8, 941, 34, 8, 23), + BuildLineMapping(93, 3, 8, 1156, 42, 8, 21), + BuildLineMapping(129, 4, 8, 1369, 50, 8, 24), + }; + + // Act and Assert + RunDesignTimeTest(host, "InjectWithSemicolon", expectedLineMappings); + } + [Fact] public void ModelVisitor_GeneratesCorrectLineMappings() { diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/InjectWithSemicolon.cshtml b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/InjectWithSemicolon.cshtml new file mode 100644 index 0000000000..d840486787 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Input/InjectWithSemicolon.cshtml @@ -0,0 +1,5 @@ +@model MyModel +@inject MyApp MyPropertyName; +@inject MyService Html; +@inject MyApp MyPropertyName2 ; +@inject MyService Html2 ; \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.cs new file mode 100644 index 0000000000..76df5b3879 --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/DesignTime/InjectWithSemicolon.cs @@ -0,0 +1,69 @@ +namespace Asp +{ + using System.Threading.Tasks; + + public class ASPV_TestFiles_Input_InjectWithSemicolon_cshtml : Microsoft.AspNet.Mvc.Razor.RazorPage< +#line 1 "TestFiles/Input/InjectWithSemicolon.cshtml" + MyModel + +#line default +#line hidden + > + { + private static object @__o; + private void @__RazorDesignTimeHelpers__() + { + #pragma warning disable 219 + #pragma warning restore 219 + } + #line hidden + public ASPV_TestFiles_Input_InjectWithSemicolon_cshtml() + { + } + #line hidden + [Microsoft.AspNet.Mvc.ActivateAttribute] + public +#line 2 "TestFiles/Input/InjectWithSemicolon.cshtml" + MyApp MyPropertyName + +#line default +#line hidden + { get; private set; } + [Microsoft.AspNet.Mvc.ActivateAttribute] + public +#line 3 "TestFiles/Input/InjectWithSemicolon.cshtml" + MyService Html + +#line default +#line hidden + { get; private set; } + [Microsoft.AspNet.Mvc.ActivateAttribute] + public +#line 4 "TestFiles/Input/InjectWithSemicolon.cshtml" + MyApp MyPropertyName2 + +#line default +#line hidden + { get; private set; } + [Microsoft.AspNet.Mvc.ActivateAttribute] + public +#line 5 "TestFiles/Input/InjectWithSemicolon.cshtml" + MyService Html2 + +#line default +#line hidden + { get; private set; } + [Microsoft.AspNet.Mvc.ActivateAttribute] + public Microsoft.AspNet.Mvc.IViewComponentHelper Component { get; private set; } + [Microsoft.AspNet.Mvc.ActivateAttribute] + public Microsoft.AspNet.Mvc.IUrlHelper Url { get; private set; } + + #line hidden + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + } + #pragma warning restore 1998 + } +} diff --git a/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/InjectWithSemicolon.cs b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/InjectWithSemicolon.cs new file mode 100644 index 0000000000..651cc1140f --- /dev/null +++ b/test/Microsoft.AspNet.Mvc.Razor.Host.Test/TestFiles/Output/Runtime/InjectWithSemicolon.cs @@ -0,0 +1,45 @@ +#pragma checksum "TestFiles/Input/InjectWithSemicolon.cshtml" "{ff1816ec-aa5e-4d10-87f7-6f4963833460}" "b753615982659a9805e6213ceced76ba06782038" +namespace Asp +{ + using System; + using System.Linq; + using System.Collections.Generic; + using Microsoft.AspNet.Mvc; + using Microsoft.AspNet.Mvc.Rendering; + using System.Threading.Tasks; + + public class ASPV_TestFiles_Input_InjectWithSemicolon_cshtml : Microsoft.AspNet.Mvc.Razor.RazorPage< +#line 1 "TestFiles/Input/InjectWithSemicolon.cshtml" + MyModel + +#line default +#line hidden + > + { + #line hidden + public ASPV_TestFiles_Input_InjectWithSemicolon_cshtml() + { + } + #line hidden + [Microsoft.AspNet.Mvc.ActivateAttribute] + public MyApp MyPropertyName { get; private set; } + [Microsoft.AspNet.Mvc.ActivateAttribute] + public MyService Html { get; private set; } + [Microsoft.AspNet.Mvc.ActivateAttribute] + public MyApp MyPropertyName2 { get; private set; } + [Microsoft.AspNet.Mvc.ActivateAttribute] + public MyService Html2 { get; private set; } + [Microsoft.AspNet.Mvc.ActivateAttribute] + public Microsoft.AspNet.Mvc.IViewComponentHelper Component { get; private set; } + [Microsoft.AspNet.Mvc.ActivateAttribute] + public Microsoft.AspNet.Mvc.IUrlHelper Url { get; private set; } + + #line hidden + + #pragma warning disable 1998 + public override async Task ExecuteAsync() + { + } + #pragma warning restore 1998 + } +} From 6c21b40894ef93d8f97072991426a42ed324c074 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 20 Jan 2015 16:29:46 -0800 Subject: [PATCH 116/118] EntryLinkHelpers.ContentLink should be available to use for user code inside of a cache tag helper's body. Fixes: #1867 --- .../Components/FeaturedMoviesComponent.cs | 44 +++++ .../Controllers/MoviesController.cs | 45 ++++++ .../Models/FeaturedMovies.cs | 16 ++ .../Services/MoviesService.cs | 68 ++++++++ samples/TagHelperSample.Web/Startup.cs | 2 + .../Views/Movies/Index.cshtml | 43 +++++ .../Components/FeaturedMovies/Default.cshtml | 17 ++ .../CacheTagHelper.cs | 15 +- .../MvcTagHelpersTests.cs | 40 +++++ .../CacheTagHelperTest.cs | 153 +++++++++++++++++- .../Components/ProductsViewComponent.cs | 25 +++ .../Catalog_CacheTagHelperController.cs | 19 +++ .../MvcTagHelpersWebSite/ProductsService.cs | 34 ++++ test/WebSites/MvcTagHelpersWebSite/Startup.cs | 1 + .../ListCategories.cshtml | 4 + .../Shared/Components/Products/Default.cshtml | 1 + 16 files changed, 518 insertions(+), 9 deletions(-) create mode 100644 samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs create mode 100644 samples/TagHelperSample.Web/Controllers/MoviesController.cs create mode 100644 samples/TagHelperSample.Web/Models/FeaturedMovies.cs create mode 100644 samples/TagHelperSample.Web/Services/MoviesService.cs create mode 100644 samples/TagHelperSample.Web/Views/Movies/Index.cshtml create mode 100644 samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/ProductsService.cs create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml create mode 100644 test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Products/Default.cshtml diff --git a/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs b/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs new file mode 100644 index 0000000000..3726cfe781 --- /dev/null +++ b/samples/TagHelperSample.Web/Components/FeaturedMoviesComponent.cs @@ -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); + } + } +} \ No newline at end of file diff --git a/samples/TagHelperSample.Web/Controllers/MoviesController.cs b/samples/TagHelperSample.Web/Controllers/MoviesController.cs new file mode 100644 index 0000000000..7c767f9487 --- /dev/null +++ b/samples/TagHelperSample.Web/Controllers/MoviesController.cs @@ -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"); + } + } +} \ No newline at end of file diff --git a/samples/TagHelperSample.Web/Models/FeaturedMovies.cs b/samples/TagHelperSample.Web/Models/FeaturedMovies.cs new file mode 100644 index 0000000000..2ec4ab9954 --- /dev/null +++ b/samples/TagHelperSample.Web/Models/FeaturedMovies.cs @@ -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; } + } +} \ No newline at end of file diff --git a/samples/TagHelperSample.Web/Services/MoviesService.cs b/samples/TagHelperSample.Web/Services/MoviesService.cs new file mode 100644 index 0000000000..0f0ad259e4 --- /dev/null +++ b/samples/TagHelperSample.Web/Services/MoviesService.cs @@ -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 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 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) }; + } + } +} \ No newline at end of file diff --git a/samples/TagHelperSample.Web/Startup.cs b/samples/TagHelperSample.Web/Startup.cs index 0742f29a6f..98c5de0397 100644 --- a/samples/TagHelperSample.Web/Startup.cs +++ b/samples/TagHelperSample.Web/Startup.cs @@ -4,6 +4,7 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; using Microsoft.Framework.DependencyInjection; +using TagHelperSample.Web.Services; namespace TagHelperSample.Web { @@ -18,6 +19,7 @@ namespace TagHelperSample.Web // Setup services with a test AssemblyProvider so that only the sample's assemblies are loaded. This // prevents loading controllers from other assemblies when the sample is used in functional tests. services.AddTransient>(); + services.AddSingleton(); services.Configure(options => { diff --git a/samples/TagHelperSample.Web/Views/Movies/Index.cshtml b/samples/TagHelperSample.Web/Views/Movies/Index.cshtml new file mode 100644 index 0000000000..780ae36505 --- /dev/null +++ b/samples/TagHelperSample.Web/Views/Movies/Index.cshtml @@ -0,0 +1,43 @@ + + + + @ViewBag.Title + + + +
+

Watch the greatest movies right here!

+ Submit your movie rankings: + + + Movies + ratings go here + + + +
+ Movies + ratings go here + +
+
+ +
+ + diff --git a/samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml b/samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml new file mode 100644 index 0000000000..8348bffa01 --- /dev/null +++ b/samples/TagHelperSample.Web/Views/Shared/Components/FeaturedMovies/Default.cshtml @@ -0,0 +1,17 @@ +@model IEnumerable + +
+ @foreach (var movie in Model) + { +
(@movie.Rank) @movie.Name
+
+
+ @movie.Description +
+ Critics say: + + @Component.Invoke("FeaturedMovies", movie.Name) + +
+ } +
\ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs index 6d8e8d288c..c177c9fa99 100644 --- a/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs +++ b/src/Microsoft.AspNet.Mvc.TagHelpers/CacheTagHelper.cs @@ -115,10 +115,17 @@ namespace Microsoft.AspNet.Mvc.TagHelpers string result; if (!MemoryCache.TryGetValue(key, out result)) { - result = await context.GetChildContentAsync(); + // Create an EntryLink and flow it so that it is accessible via the ambient EntryLinkHelpers.ContentLink + // for user code. + var entryLink = new EntryLink(); + using (entryLink.FlowContext()) + { + result = await context.GetChildContentAsync(); + } + MemoryCache.Set(key, cacheSetContext => { - UpdateCacheContext(cacheSetContext); + UpdateCacheContext(cacheSetContext, entryLink); return result; }); } @@ -171,7 +178,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers } // Internal for unit testing - internal void UpdateCacheContext(ICacheSetContext cacheSetContext) + internal void UpdateCacheContext(ICacheSetContext cacheSetContext, EntryLink entryLink) { if (ExpiresOn != null) { @@ -192,6 +199,8 @@ namespace Microsoft.AspNet.Mvc.TagHelpers { cacheSetContext.SetPriority(Priority.Value); } + + cacheSetContext.AddEntryLink(entryLink); } private static void AddStringCollectionKey(StringBuilder builder, diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs index fc975bae58..14032856f6 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/MvcTagHelpersTests.cs @@ -286,5 +286,45 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests Assert.Equal(expected2, response3.Trim()); Assert.Equal(expected2, response4.Trim()); } + + [Fact] + public async Task CacheTagHelper_BubblesExpirationOfNestedTagHelpers() + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + client.BaseAddress = new Uri("http://localhost"); + + // Act - 1 + var response1 = await client.GetStringAsync("/categories/Books?correlationId=1"); + + // Assert - 1 + var expected1 = +@"Category: Books +Products: Book1, Book2 (1)"; + Assert.Equal(expected1, response1.Trim()); + + // Act - 2 + var response2 = await client.GetStringAsync("/categories/Electronics?correlationId=2"); + + // Assert - 2 + var expected2 = +@"Category: Electronics +Products: Book1, Book2 (1)"; + Assert.Equal(expected2, response2.Trim()); + + // Act - 3 + // Trigger an expiration + var response3 = await client.PostAsync("/categories/update-products", new StringContent(string.Empty)); + response3.EnsureSuccessStatusCode(); + + var response4 = await client.GetStringAsync("/categories/Electronics?correlationId=3"); + + // Assert - 3 + var expected3 = +@"Category: Electronics +Products: Laptops (3)"; + Assert.Equal(expected3, response4.Trim()); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs index 923eaf9446..8b91ecac88 100644 --- a/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.TagHelpers.Test/CacheTagHelperTest.cs @@ -7,14 +7,16 @@ using System.IO; using System.Security.Claims; using System.Security.Cryptography; using System.Text; +using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Mvc.ModelBinding; using Microsoft.AspNet.Mvc.Rendering; -using Microsoft.AspNet.Http.Core; using Microsoft.AspNet.Razor.Runtime.TagHelpers; using Microsoft.AspNet.Routing; using Microsoft.Framework.Cache.Memory; using Microsoft.Framework.Cache.Memory.Infrastructure; +using Microsoft.Framework.Expiration.Interfaces; using Moq; using Xunit; @@ -345,7 +347,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers // Arrange var expiresOn = DateTimeOffset.UtcNow.AddMinutes(4); var cache = new MemoryCache(new MemoryCacheOptions()); - var cacheContext = new Mock(); + var cacheContext = new Mock(MockBehavior.Strict); cacheContext.Setup(c => c.SetAbsoluteExpiration(expiresOn)) .Verifiable(); var cacheTagHelper = new CacheTagHelper @@ -355,7 +357,64 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object); + cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); + + // Assert + cacheContext.Verify(); + } + + [Fact] + public void UpdateCacheContext_UsesAbsoluteExpirationSpecifiedOnEntryLink() + { + // Arrange + var expiresOn = DateTimeOffset.UtcNow.AddMinutes(7); + var cache = new MemoryCache(new MemoryCacheOptions()); + var cacheContext = new Mock(MockBehavior.Strict); + cacheContext.Setup(c => c.SetAbsoluteExpiration(expiresOn)) + .Verifiable(); + var cacheTagHelper = new CacheTagHelper + { + MemoryCache = cache + }; + + var entryLink = new EntryLink(); + entryLink.SetAbsoluteExpiration(expiresOn); + + // Act + cacheTagHelper.UpdateCacheContext(cacheContext.Object, entryLink); + + // Assert + cacheContext.Verify(); + } + + [Fact] + public void UpdateCacheContext_PrefersAbsoluteExpirationSpecifiedOnEntryLinkOverExpiresOn() + { + // Arrange + var expiresOn1 = DateTimeOffset.UtcNow.AddDays(12); + var expiresOn2 = DateTimeOffset.UtcNow.AddMinutes(4); + var cache = new MemoryCache(new MemoryCacheOptions()); + var cacheContext = new Mock(); + var sequence = new MockSequence(); + cacheContext.InSequence(sequence) + .Setup(c => c.SetAbsoluteExpiration(expiresOn1)) + .Verifiable(); + + cacheContext.InSequence(sequence) + .Setup(c => c.SetAbsoluteExpiration(expiresOn2)) + .Verifiable(); + + var cacheTagHelper = new CacheTagHelper + { + MemoryCache = cache, + ExpiresOn = expiresOn1 + }; + + var entryLink = new EntryLink(); + entryLink.SetAbsoluteExpiration(expiresOn2); + + // Act + cacheTagHelper.UpdateCacheContext(cacheContext.Object, entryLink); // Assert cacheContext.Verify(); @@ -377,7 +436,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object); + cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); // Assert cacheContext.Verify(); @@ -399,7 +458,7 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object); + cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); // Assert cacheContext.Verify(); @@ -421,12 +480,43 @@ namespace Microsoft.AspNet.Mvc.TagHelpers }; // Act - cacheTagHelper.UpdateCacheContext(cacheContext.Object); + cacheTagHelper.UpdateCacheContext(cacheContext.Object, new EntryLink()); // Assert cacheContext.Verify(); } + [Fact] + public void UpdateCacheContext_CopiesTriggersFromEntryLink() + { + // Arrange + var expiresSliding = TimeSpan.FromSeconds(30); + var expected = new[] { Mock.Of(), Mock.Of() }; + var triggers = new List(); + var cache = new MemoryCache(new MemoryCacheOptions()); + var cacheContext = new Mock(); + cacheContext.Setup(c => c.SetSlidingExpiration(expiresSliding)) + .Verifiable(); + cacheContext.Setup(c => c.AddExpirationTrigger(It.IsAny())) + .Callback(triggers.Add) + .Verifiable(); + var cacheTagHelper = new CacheTagHelper + { + MemoryCache = cache, + ExpiresSliding = expiresSliding + }; + + var entryLink = new EntryLink(); + entryLink.AddExpirationTriggers(expected); + + // Act + cacheTagHelper.UpdateCacheContext(cacheContext.Object, entryLink); + + // Assert + cacheContext.Verify(); + Assert.Equal(expected, triggers); + } + [Fact] public async Task ProcessAsync_UsesExpiresAfter_ToExpireCacheEntry() { @@ -604,6 +694,57 @@ namespace Microsoft.AspNet.Mvc.TagHelpers Assert.Equal(childContent2, tagHelperOutput2.Content); } + [Fact] + public async Task ProcessAsync_FlowsEntryLinkThatAllowsAddingTriggersToAddedEntry() + { + // Arrange + var id = "some-id"; + var expectedContent = "some-content"; + var tokenSource = new CancellationTokenSource(); + var cache = new MemoryCache(new MemoryCacheOptions()); + var tagHelperContext = new TagHelperContext(new Dictionary(), + id, + () => + { + var entryLink = EntryLinkHelpers.ContextLink; + Assert.NotNull(entryLink); + entryLink.AddExpirationTriggers(new[] + { + new CancellationTokenTrigger(tokenSource.Token) + }); + return Task.FromResult(expectedContent); + }); + var tagHelperOutput = new TagHelperOutput("cache", new Dictionary()) + { + PreContent = "", + PostContent = "" + }; + var cacheTagHelper = new CacheTagHelper + { + ViewContext = GetViewContext(), + MemoryCache = cache, + }; + var key = cacheTagHelper.GenerateKey(tagHelperContext); + + // Act - 1 + await cacheTagHelper.ProcessAsync(tagHelperContext, tagHelperOutput); + string cachedValue; + var result = cache.TryGetValue(key, out cachedValue); + + // Assert - 1 + Assert.Equal(expectedContent, tagHelperOutput.Content); + Assert.True(result); + Assert.Equal(expectedContent, cachedValue); + + // Act - 2 + tokenSource.Cancel(); + result = cache.TryGetValue(key, out cachedValue); + + // Assert - 2 + Assert.False(result); + Assert.Null(cachedValue); + } + private static ViewContext GetViewContext() { var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); diff --git a/test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.cs b/test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.cs new file mode 100644 index 0000000000..10d7e8e32d --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Components/ProductsViewComponent.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 Microsoft.AspNet.Mvc; +using Microsoft.Framework.Cache.Memory; +using Microsoft.Framework.Expiration.Interfaces; + +namespace MvcTagHelpersWebSite.Components +{ + public class ProductsViewComponent : ViewComponent + { + [Activate] + public ProductsService ProductsService { get; set; } + + public IViewComponentResult Invoke(string category) + { + IExpirationTrigger trigger; + var products = ProductsService.GetProducts(category, out trigger); + EntryLinkHelpers.ContextLink.AddExpirationTriggers(new[] { trigger }); + + ViewData["Products"] = products; + return View(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs b/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs index 1f83b6e0f4..3fb0549183 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs +++ b/test/WebSites/MvcTagHelpersWebSite/Controllers/Catalog_CacheTagHelperController.cs @@ -8,6 +8,9 @@ namespace MvcTagHelpersWebSite.Controllers { public class Catalog_CacheTagHelperController : Controller { + [Activate] + public ProductsService ProductsService { get; set; } + [HttpGet("/catalog")] public ViewResult Splash(int categoryId, int correlationId, [FromHeader]string locale) { @@ -61,5 +64,21 @@ namespace MvcTagHelpersWebSite.Controllers ViewData["CorrelationId"] = correlationId; return View(); } + + [HttpGet("/categories/{category}")] + public ViewResult ListCategories(string category, int correlationId) + { + ViewData["Category"] = category; + ViewData["CorrelationId"] = correlationId; + + return View(); + } + + [HttpPost("/categories/update-products")] + public IActionResult UpdateCategories() + { + ProductsService.UpdateProducts(); + return new EmptyResult(); + } } } diff --git a/test/WebSites/MvcTagHelpersWebSite/ProductsService.cs b/test/WebSites/MvcTagHelpersWebSite/ProductsService.cs new file mode 100644 index 0000000000..0de16735f3 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/ProductsService.cs @@ -0,0 +1,34 @@ +// 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; +using Microsoft.Framework.Cache.Memory; +using Microsoft.Framework.Expiration.Interfaces; + +namespace MvcTagHelpersWebSite +{ + public class ProductsService + { + private readonly CancellationTokenSource _tokenSource = new CancellationTokenSource(); + + public string GetProducts(string category, out IExpirationTrigger trigger) + { + var token = _tokenSource.IsCancellationRequested ? CancellationToken.None : + _tokenSource.Token; + trigger = new CancellationTokenTrigger(token); + if (category == "Books") + { + return "Book1, Book2"; + } + else + { + return "Laptops"; + } + } + + public void UpdateProducts() + { + _tokenSource.Cancel(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Startup.cs b/test/WebSites/MvcTagHelpersWebSite/Startup.cs index 067828ecdf..516d70e552 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Startup.cs +++ b/test/WebSites/MvcTagHelpersWebSite/Startup.cs @@ -16,6 +16,7 @@ namespace MvcTagHelpersWebSite app.UseServices(services => { services.AddMvc(configuration); + services.AddSingleton(); }); app.UseMvc(routes => diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml new file mode 100644 index 0000000000..ff12c2e201 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml @@ -0,0 +1,4 @@ + +Category: @ViewBag.Category +@Component.Invoke("Products", ViewBag.Category) + \ No newline at end of file diff --git a/test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Products/Default.cshtml b/test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Products/Default.cshtml new file mode 100644 index 0000000000..bb28a8aad0 --- /dev/null +++ b/test/WebSites/MvcTagHelpersWebSite/Views/Shared/Components/Products/Default.cshtml @@ -0,0 +1 @@ +Products: @ViewBag.Products (@ViewBag.CorrelationId) \ No newline at end of file From 5407ff3bd66e2cf9ddea43b61d40d009680e79ab Mon Sep 17 00:00:00 2001 From: Wei Wang Date: Thu, 22 Jan 2015 17:35:46 -0800 Subject: [PATCH 117/118] React to kpm renaming --- test/WebSites/LoggingWebSite/project.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/WebSites/LoggingWebSite/project.json b/test/WebSites/LoggingWebSite/project.json index 5e14c7f6e7..895e88892b 100644 --- a/test/WebSites/LoggingWebSite/project.json +++ b/test/WebSites/LoggingWebSite/project.json @@ -4,7 +4,7 @@ "exclude": [ "wwwroot" ], - "packExclude": [ + "bundleExclude": [ "**.kproj", "**.user", "**.vspscc" From 071c6973182f2bcbee580764efd6e6717cd72ebf Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 23 Jan 2015 12:50:21 -0800 Subject: [PATCH 118/118] Modify BuilderExtensions.UseMvc to not add any routes by default Fixes #1879 --- samples/MvcSample.Web/Startup.cs | 2 -- samples/TagHelperSample.Web/Startup.cs | 7 ++++++- .../Routing/RouteBuilderExtensions.cs | 3 ++- src/Microsoft.AspNet.Mvc/BuilderExtensions.cs | 21 ++++++++++++++----- .../UrlHelperTest.cs | 1 + .../ActionConstraintsWebSite/Startup.cs | 1 - test/WebSites/ActionResultsWebSite/Startup.cs | 1 - test/WebSites/ActivatorWebSite/Startup.cs | 1 - test/WebSites/AddServicesWebSite/Startup.cs | 1 - test/WebSites/AntiForgeryWebSite/Startup.cs | 1 - test/WebSites/ApiExplorerWebSite/Startup.cs | 1 - .../ApplicationModelWebSite/Startup.cs | 7 ++++++- test/WebSites/AutofacWebSite/Startup.cs | 1 - test/WebSites/BasicWebSite/Startup.cs | 1 - .../CompositeViewEngineWebSite/Startup.cs | 7 ++++++- test/WebSites/ConnegWebSite/Startup.cs | 1 - .../CustomRouteWebSite/LocalizedRoute.cs | 2 +- test/WebSites/CustomRouteWebSite/Startup.cs | 1 - test/WebSites/FilesWebSite/Startup.cs | 1 - test/WebSites/FiltersWebSite/Startup.cs | 7 ++++++- test/WebSites/FormatterWebSite/Startup.cs | 1 - .../InlineConstraintsWebSite/Startup.cs | 1 - test/WebSites/LoggingWebSite/Startup.cs | 1 - test/WebSites/ModelBindingWebSite/Startup.cs | 7 ++++++- test/WebSites/MvcTagHelpersWebSite/Startup.cs | 1 - .../WebSites/PrecompilationWebSite/Startup.cs | 7 ++++++- .../RazorInstrumentationWebsite/Startup.cs | 7 ++++++- .../RazorViewEngineOptionsWebsite/Startup.cs | 2 -- test/WebSites/RazorWebSite/Startup.cs | 7 ++++++- .../RequestServicesWebSite/Startup.cs | 7 ++++++- test/WebSites/ResponseCacheWebSite/Startup.cs | 7 ++++++- test/WebSites/RoutingWebSite/Startup.cs | 1 - test/WebSites/TagHelpersWebSite/Startup.cs | 7 ++++++- test/WebSites/UrlHelperWebSite/Startup.cs | 1 - .../WebSites/ValueProvidersWebSite/Startup.cs | 7 ++++++- test/WebSites/VersioningWebSite/Startup.cs | 7 ++++++- test/WebSites/ViewComponentWebSite/Startup.cs | 8 ++++++- 37 files changed, 105 insertions(+), 42 deletions(-) diff --git a/samples/MvcSample.Web/Startup.cs b/samples/MvcSample.Web/Startup.cs index 7410c6b95f..64afcbb810 100644 --- a/samples/MvcSample.Web/Startup.cs +++ b/samples/MvcSample.Web/Startup.cs @@ -2,12 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; using System.Security.Claims; using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.Razor; -using Microsoft.AspNet.Routing; using Microsoft.AspNet.Security; using Microsoft.Framework.ConfigurationModel; using Microsoft.Framework.DependencyInjection; diff --git a/samples/TagHelperSample.Web/Startup.cs b/samples/TagHelperSample.Web/Startup.cs index 98c5de0397..4e4a8f20eb 100644 --- a/samples/TagHelperSample.Web/Startup.cs +++ b/samples/TagHelperSample.Web/Startup.cs @@ -26,7 +26,12 @@ namespace TagHelperSample.Web options.AddXmlDataContractSerializerFormatter(); }); }); - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } diff --git a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Routing/RouteBuilderExtensions.cs b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Routing/RouteBuilderExtensions.cs index 7cf2cadbd2..de737317fb 100644 --- a/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Routing/RouteBuilderExtensions.cs +++ b/src/Microsoft.AspNet.Mvc.WebApiCompatShim/Routing/RouteBuilderExtensions.cs @@ -3,9 +3,10 @@ using System.Collections.Generic; using Microsoft.AspNet.Mvc.WebApiCompatShim; +using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Constraints; -namespace Microsoft.AspNet.Routing +namespace Microsoft.AspNet.Builder { public static class RouteBuilderExtensions { diff --git a/src/Microsoft.AspNet.Mvc/BuilderExtensions.cs b/src/Microsoft.AspNet.Mvc/BuilderExtensions.cs index 438d916a08..679a81df05 100644 --- a/src/Microsoft.AspNet.Mvc/BuilderExtensions.cs +++ b/src/Microsoft.AspNet.Mvc/BuilderExtensions.cs @@ -9,20 +9,31 @@ using Microsoft.AspNet.Routing; namespace Microsoft.AspNet.Builder { + /// + /// Extension methods for to add Mvc to the request execution pipeline. + /// public static class BuilderExtensions { + /// + /// Adds Mvc to the request execution pipeline. + /// + /// The . + /// The . + /// This method only supports attribute routing. To add conventional routes use + /// . public static IApplicationBuilder UseMvc([NotNull] this IApplicationBuilder app) { return app.UseMvc(routes => { - // Action style actions - routes.MapRoute(null, "{controller}/{action}/{id?}", new { controller = "Home", action = "Index" }); - - // Rest style actions - routes.MapRoute(null, "{controller}/{id?}"); }); } + /// + /// Adds Mvc to the request execution pipeline. + /// + /// The . + /// A callback to configure Mvc routes. + /// The . public static IApplicationBuilder UseMvc( [NotNull] this IApplicationBuilder app, [NotNull] Action configureRoutes) diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs index 79b911f37a..cc2afb46c9 100644 --- a/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs +++ b/test/Microsoft.AspNet.Mvc.Core.Test/UrlHelperTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.AspNet.Builder; using Microsoft.AspNet.Http; using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; diff --git a/test/WebSites/ActionConstraintsWebSite/Startup.cs b/test/WebSites/ActionConstraintsWebSite/Startup.cs index f01b2f2974..3444800f8b 100644 --- a/test/WebSites/ActionConstraintsWebSite/Startup.cs +++ b/test/WebSites/ActionConstraintsWebSite/Startup.cs @@ -3,7 +3,6 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace ActionConstraintsWebSite diff --git a/test/WebSites/ActionResultsWebSite/Startup.cs b/test/WebSites/ActionResultsWebSite/Startup.cs index d8c6760cf9..f481be6066 100644 --- a/test/WebSites/ActionResultsWebSite/Startup.cs +++ b/test/WebSites/ActionResultsWebSite/Startup.cs @@ -3,7 +3,6 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace ActionResultsWebSite diff --git a/test/WebSites/ActivatorWebSite/Startup.cs b/test/WebSites/ActivatorWebSite/Startup.cs index c48637d7f9..16b35b0df3 100644 --- a/test/WebSites/ActivatorWebSite/Startup.cs +++ b/test/WebSites/ActivatorWebSite/Startup.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace ActivatorWebSite diff --git a/test/WebSites/AddServicesWebSite/Startup.cs b/test/WebSites/AddServicesWebSite/Startup.cs index 4cb90bd753..ea551fee82 100644 --- a/test/WebSites/AddServicesWebSite/Startup.cs +++ b/test/WebSites/AddServicesWebSite/Startup.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Routing; namespace AddServicesWebSite { diff --git a/test/WebSites/AntiForgeryWebSite/Startup.cs b/test/WebSites/AntiForgeryWebSite/Startup.cs index 508c1e4378..1fa0467c35 100644 --- a/test/WebSites/AntiForgeryWebSite/Startup.cs +++ b/test/WebSites/AntiForgeryWebSite/Startup.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace AntiForgeryWebSite diff --git a/test/WebSites/ApiExplorerWebSite/Startup.cs b/test/WebSites/ApiExplorerWebSite/Startup.cs index 34b77d4717..8fe6435c0c 100644 --- a/test/WebSites/ApiExplorerWebSite/Startup.cs +++ b/test/WebSites/ApiExplorerWebSite/Startup.cs @@ -3,7 +3,6 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace ApiExplorerWebSite diff --git a/test/WebSites/ApplicationModelWebSite/Startup.cs b/test/WebSites/ApplicationModelWebSite/Startup.cs index b736e8dc42..0773fe961a 100644 --- a/test/WebSites/ApplicationModelWebSite/Startup.cs +++ b/test/WebSites/ApplicationModelWebSite/Startup.cs @@ -17,7 +17,12 @@ namespace ApplicationModelWebSite services.AddMvc(configuration); }); - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller}/{action}/{id?}"); + }); } } } diff --git a/test/WebSites/AutofacWebSite/Startup.cs b/test/WebSites/AutofacWebSite/Startup.cs index b055590bb0..82b13cc33a 100644 --- a/test/WebSites/AutofacWebSite/Startup.cs +++ b/test/WebSites/AutofacWebSite/Startup.cs @@ -4,7 +4,6 @@ using System; using Autofac; using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; using Microsoft.Framework.DependencyInjection.Autofac; diff --git a/test/WebSites/BasicWebSite/Startup.cs b/test/WebSites/BasicWebSite/Startup.cs index 8ebcd53366..ee9a33d521 100644 --- a/test/WebSites/BasicWebSite/Startup.cs +++ b/test/WebSites/BasicWebSite/Startup.cs @@ -3,7 +3,6 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace BasicWebSite diff --git a/test/WebSites/CompositeViewEngineWebSite/Startup.cs b/test/WebSites/CompositeViewEngineWebSite/Startup.cs index 8dec22ad38..3351b3bc7d 100644 --- a/test/WebSites/CompositeViewEngineWebSite/Startup.cs +++ b/test/WebSites/CompositeViewEngineWebSite/Startup.cs @@ -25,7 +25,12 @@ namespace CompositeViewEngineWebSite }); // Add MVC to the request pipeline - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } diff --git a/test/WebSites/ConnegWebSite/Startup.cs b/test/WebSites/ConnegWebSite/Startup.cs index fd4e143ecf..f3075f4dce 100644 --- a/test/WebSites/ConnegWebSite/Startup.cs +++ b/test/WebSites/ConnegWebSite/Startup.cs @@ -3,7 +3,6 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace ConnegWebSite diff --git a/test/WebSites/CustomRouteWebSite/LocalizedRoute.cs b/test/WebSites/CustomRouteWebSite/LocalizedRoute.cs index 61d2f85db2..638a0e96d7 100644 --- a/test/WebSites/CustomRouteWebSite/LocalizedRoute.cs +++ b/test/WebSites/CustomRouteWebSite/LocalizedRoute.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; -using Microsoft.AspNet.Routing; using Microsoft.AspNet.Http; +using Microsoft.AspNet.Routing; namespace CustomRouteWebSite { diff --git a/test/WebSites/CustomRouteWebSite/Startup.cs b/test/WebSites/CustomRouteWebSite/Startup.cs index 2d39cc65b9..af617b211f 100644 --- a/test/WebSites/CustomRouteWebSite/Startup.cs +++ b/test/WebSites/CustomRouteWebSite/Startup.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace CustomRouteWebSite diff --git a/test/WebSites/FilesWebSite/Startup.cs b/test/WebSites/FilesWebSite/Startup.cs index 59c706ca1a..b178078551 100644 --- a/test/WebSites/FilesWebSite/Startup.cs +++ b/test/WebSites/FilesWebSite/Startup.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace FilesWebSite diff --git a/test/WebSites/FiltersWebSite/Startup.cs b/test/WebSites/FiltersWebSite/Startup.cs index 70ea5da1c3..848e9ed791 100644 --- a/test/WebSites/FiltersWebSite/Startup.cs +++ b/test/WebSites/FiltersWebSite/Startup.cs @@ -50,7 +50,12 @@ namespace FiltersWebSite app.UseMiddleware(); - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } diff --git a/test/WebSites/FormatterWebSite/Startup.cs b/test/WebSites/FormatterWebSite/Startup.cs index 07ebad5684..d26a72dab9 100644 --- a/test/WebSites/FormatterWebSite/Startup.cs +++ b/test/WebSites/FormatterWebSite/Startup.cs @@ -3,7 +3,6 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace FormatterWebSite diff --git a/test/WebSites/InlineConstraintsWebSite/Startup.cs b/test/WebSites/InlineConstraintsWebSite/Startup.cs index 698deb6c3c..26fc58fac7 100644 --- a/test/WebSites/InlineConstraintsWebSite/Startup.cs +++ b/test/WebSites/InlineConstraintsWebSite/Startup.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Routing; using Microsoft.AspNet.Routing.Constraints; using Microsoft.Framework.DependencyInjection; diff --git a/test/WebSites/LoggingWebSite/Startup.cs b/test/WebSites/LoggingWebSite/Startup.cs index dbb5560c59..cdf7947d01 100644 --- a/test/WebSites/LoggingWebSite/Startup.cs +++ b/test/WebSites/LoggingWebSite/Startup.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace LoggingWebSite diff --git a/test/WebSites/ModelBindingWebSite/Startup.cs b/test/WebSites/ModelBindingWebSite/Startup.cs index ede76f9cb0..d300caa990 100644 --- a/test/WebSites/ModelBindingWebSite/Startup.cs +++ b/test/WebSites/ModelBindingWebSite/Startup.cs @@ -37,7 +37,12 @@ namespace ModelBindingWebSite app.UseErrorReporter(); // Add MVC to the request pipeline - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } diff --git a/test/WebSites/MvcTagHelpersWebSite/Startup.cs b/test/WebSites/MvcTagHelpersWebSite/Startup.cs index 516d70e552..ce76e91c61 100644 --- a/test/WebSites/MvcTagHelpersWebSite/Startup.cs +++ b/test/WebSites/MvcTagHelpersWebSite/Startup.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace MvcTagHelpersWebSite diff --git a/test/WebSites/PrecompilationWebSite/Startup.cs b/test/WebSites/PrecompilationWebSite/Startup.cs index eb9a98053d..c7dfac319a 100644 --- a/test/WebSites/PrecompilationWebSite/Startup.cs +++ b/test/WebSites/PrecompilationWebSite/Startup.cs @@ -17,7 +17,12 @@ namespace PrecompilationWebSite services.AddMvc(configuration); }); - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } diff --git a/test/WebSites/RazorInstrumentationWebsite/Startup.cs b/test/WebSites/RazorInstrumentationWebsite/Startup.cs index a14ea6cfba..755f65e2b6 100644 --- a/test/WebSites/RazorInstrumentationWebsite/Startup.cs +++ b/test/WebSites/RazorInstrumentationWebsite/Startup.cs @@ -36,7 +36,12 @@ namespace RazorInstrumentationWebSite }); // Add MVC to the request pipeline - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } diff --git a/test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs b/test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs index 636c9535d7..f1b17c61c5 100644 --- a/test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs +++ b/test/WebSites/RazorViewEngineOptionsWebsite/Startup.cs @@ -1,13 +1,11 @@ // 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.IO; using System.Reflection; using Microsoft.AspNet.Builder; using Microsoft.AspNet.FileProviders; using Microsoft.AspNet.Mvc.Razor; using Microsoft.Framework.DependencyInjection; -using Microsoft.AspNet.Routing; namespace RazorViewEngineOptionsWebsite { diff --git a/test/WebSites/RazorWebSite/Startup.cs b/test/WebSites/RazorWebSite/Startup.cs index 91a7547720..02c8d82c90 100644 --- a/test/WebSites/RazorWebSite/Startup.cs +++ b/test/WebSites/RazorWebSite/Startup.cs @@ -31,7 +31,12 @@ namespace RazorWebSite }); // Add MVC to the request pipeline - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } diff --git a/test/WebSites/RequestServicesWebSite/Startup.cs b/test/WebSites/RequestServicesWebSite/Startup.cs index ccb8d7f72b..fdc6c1382e 100644 --- a/test/WebSites/RequestServicesWebSite/Startup.cs +++ b/test/WebSites/RequestServicesWebSite/Startup.cs @@ -22,7 +22,12 @@ namespace RequestServicesWebSite // Initializes the RequestId service for each request app.UseMiddleware(); - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } diff --git a/test/WebSites/ResponseCacheWebSite/Startup.cs b/test/WebSites/ResponseCacheWebSite/Startup.cs index ffc8f1e3ab..271f7437cc 100644 --- a/test/WebSites/ResponseCacheWebSite/Startup.cs +++ b/test/WebSites/ResponseCacheWebSite/Startup.cs @@ -17,7 +17,12 @@ namespace ResponseCacheWebSite services.AddMvc(configuration); }); - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index 099b773f8b..c67435d81f 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNet.Builder; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace RoutingWebSite diff --git a/test/WebSites/TagHelpersWebSite/Startup.cs b/test/WebSites/TagHelpersWebSite/Startup.cs index 6a967400cf..6b46f59b44 100644 --- a/test/WebSites/TagHelpersWebSite/Startup.cs +++ b/test/WebSites/TagHelpersWebSite/Startup.cs @@ -17,7 +17,12 @@ namespace TagHelpersWebSite services.AddMvc(configuration); }); - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } diff --git a/test/WebSites/UrlHelperWebSite/Startup.cs b/test/WebSites/UrlHelperWebSite/Startup.cs index 4ff5a32509..6371112dfb 100644 --- a/test/WebSites/UrlHelperWebSite/Startup.cs +++ b/test/WebSites/UrlHelperWebSite/Startup.cs @@ -3,7 +3,6 @@ using Microsoft.AspNet.Builder; using Microsoft.AspNet.Mvc; -using Microsoft.AspNet.Routing; using Microsoft.Framework.DependencyInjection; namespace UrlHelperWebSite diff --git a/test/WebSites/ValueProvidersWebSite/Startup.cs b/test/WebSites/ValueProvidersWebSite/Startup.cs index b833dd39bb..2126afca29 100644 --- a/test/WebSites/ValueProvidersWebSite/Startup.cs +++ b/test/WebSites/ValueProvidersWebSite/Startup.cs @@ -25,7 +25,12 @@ namespace ValueProvidersWebSite }); // Add MVC to the request pipeline - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } diff --git a/test/WebSites/VersioningWebSite/Startup.cs b/test/WebSites/VersioningWebSite/Startup.cs index a19a7ee669..1a615d644b 100644 --- a/test/WebSites/VersioningWebSite/Startup.cs +++ b/test/WebSites/VersioningWebSite/Startup.cs @@ -19,7 +19,12 @@ namespace VersioningWebSite services.AddScoped(); }); - app.UseMvc(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } } diff --git a/test/WebSites/ViewComponentWebSite/Startup.cs b/test/WebSites/ViewComponentWebSite/Startup.cs index 1629fb4954..c6ea580786 100644 --- a/test/WebSites/ViewComponentWebSite/Startup.cs +++ b/test/WebSites/ViewComponentWebSite/Startup.cs @@ -15,7 +15,13 @@ namespace ViewComponentWebSite { services.AddMvc(configuration); }); - app.UseMvc(); + + app.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); } } }