From 73f44889f2975df9754ecd011699b94b05ba3d2c Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 20 Feb 2015 12:47:09 -0800 Subject: [PATCH] ViewLocationExpanderContext should have a flag that indicates if the lookup is for a full or partial view Fixes #1212 --- .../DefaultViewLocationCache.cs | 4 +- .../RazorViewEngine.cs | 16 ++++--- .../ViewLocationExpanderContext.cs | 23 ++++++++-- .../ViewEngineTests.cs | 27 +++++++++++ .../DefaultViewLocationCacheTest.cs | 46 +++++++++++++++---- .../Controllers/ExpanderViewsController.cs | 21 +++++++++ ...tomPartialDirectoryViewLocationExpander.cs | 35 ++++++++++++++ .../Shared-Views/ExpanderViews/Index.cshtml | 3 ++ .../ExpanderViews/_ExpanderPartial.cshtml | 1 + test/WebSites/RazorWebSite/Startup.cs | 1 + .../Views/ExpanderViews/Index.cshtml | 1 + 11 files changed, 156 insertions(+), 22 deletions(-) create mode 100644 test/WebSites/RazorWebSite/Controllers/ExpanderViewsController.cs create mode 100644 test/WebSites/RazorWebSite/Services/CustomPartialDirectoryViewLocationExpander.cs create mode 100644 test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/Index.cshtml create mode 100644 test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/_ExpanderPartial.cshtml create mode 100644 test/WebSites/RazorWebSite/Views/ExpanderViews/Index.cshtml diff --git a/src/Microsoft.AspNet.Mvc.Razor/DefaultViewLocationCache.cs b/src/Microsoft.AspNet.Mvc.Razor/DefaultViewLocationCache.cs index badcd44148..9575a89eb9 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/DefaultViewLocationCache.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/DefaultViewLocationCache.cs @@ -51,8 +51,10 @@ namespace Microsoft.AspNet.Mvc.Razor var routeValues = context.ActionContext.RouteData.Values; var controller = routeValues.GetValueOrDefault(RazorViewEngine.ControllerKey); - // format is "{viewName}:{controllerName}:{areaName}:" + // format is "{viewName}:{isPartial}:{controllerName}:{areaName}:" keyBuilder.Append(context.ViewName) + .Append(CacheKeySeparator) + .Append(context.IsPartial ? 1 : 0) .Append(CacheKeySeparator) .Append(controller); diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs index 4ced6312bd..9e2693cd50 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs @@ -104,7 +104,7 @@ namespace Microsoft.AspNet.Mvc.Razor throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewName)); } - var pageResult = GetRazorPageResult(context, viewName); + var pageResult = GetRazorPageResult(context, viewName, isPartial: false); return CreateViewEngineResult(pageResult, _viewFactory, isPartial: false); } @@ -117,7 +117,7 @@ namespace Microsoft.AspNet.Mvc.Razor throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(partialViewName)); } - var pageResult = GetRazorPageResult(context, partialViewName); + var pageResult = GetRazorPageResult(context, partialViewName, isPartial: true); return CreateViewEngineResult(pageResult, _viewFactory, isPartial: true); } @@ -130,11 +130,12 @@ namespace Microsoft.AspNet.Mvc.Razor throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); } - return GetRazorPageResult(context, pageName); + return GetRazorPageResult(context, pageName, isPartial: true); } private RazorPageResult GetRazorPageResult(ActionContext context, - string pageName) + string pageName, + bool isPartial) { if (IsApplicationRelativePath(pageName)) { @@ -154,12 +155,13 @@ namespace Microsoft.AspNet.Mvc.Razor } else { - return LocatePageFromViewLocations(context, pageName); + return LocatePageFromViewLocations(context, pageName, isPartial); } } private RazorPageResult LocatePageFromViewLocations(ActionContext context, - string pageName) + string pageName, + bool isPartial) { // Initialize the dictionary for the typical case of having controller and action tokens. var routeValues = context.RouteData.Values; @@ -169,7 +171,7 @@ namespace Microsoft.AspNet.Mvc.Razor var viewLocations = !string.IsNullOrEmpty(areaName) ? AreaViewLocationFormats : ViewLocationFormats; - var expanderContext = new ViewLocationExpanderContext(context, pageName); + var expanderContext = new ViewLocationExpanderContext(context, pageName, isPartial); if (_viewLocationExpanders.Count > 0) { expanderContext.Values = new Dictionary(StringComparer.Ordinal); diff --git a/src/Microsoft.AspNet.Mvc.Razor/ViewLocationExpanderContext.cs b/src/Microsoft.AspNet.Mvc.Razor/ViewLocationExpanderContext.cs index 733f3458e7..447f531dab 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/ViewLocationExpanderContext.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/ViewLocationExpanderContext.cs @@ -11,22 +11,35 @@ namespace Microsoft.AspNet.Mvc.Razor /// public class ViewLocationExpanderContext { + /// + /// Initializes a new instance of . + /// + /// The for the current executing action. + /// The view name. + /// Determines if the view being discovered is a partial. public ViewLocationExpanderContext([NotNull] ActionContext actionContext, - [NotNull] string viewName) + [NotNull] string viewName, + bool isPartial) { ActionContext = actionContext; ViewName = viewName; + IsPartial = isPartial; } /// - /// Gets the for the current executing action. + /// Gets the for the current executing action. /// - public ActionContext ActionContext { get; private set; } + public ActionContext ActionContext { get; } /// - /// Gets the view name + /// Gets the view name. /// - public string ViewName { get; private set; } + public string ViewName { get; } + + /// + /// Gets a value that determines if a partial view is being discovered. + /// + public bool IsPartial { get; } /// /// Gets or sets the that is populated with values as part of diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs index 3fe7614b12..106491c880 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs @@ -165,6 +165,33 @@ component-content"; Assert.Equal(expected, body.Trim()); } + public static TheoryData ViewLocationExpanders_PassesInIsPartialToViewLocationExpanderContextData + { + get + { + return new TheoryData + { + { "Index", "/Shared-Views/ExpanderViews/_ExpanderPartial.cshtml" }, + { "Partial", "/Shared-Views/ExpanderViews/_ExpanderPartial.cshtml" } + }; + } + } + + [Theory] + [MemberData(nameof(ViewLocationExpanders_PassesInIsPartialToViewLocationExpanderContextData))] + public async Task ViewLocationExpanders_PassesInIsPartialToViewLocationExpanderContext(string action, string expected) + { + // Arrange + var server = TestServer.Create(_provider, _app); + var client = server.CreateClient(); + + // Act + var body = await client.GetStringAsync($"http://localhost/ExpanderViews/{action}"); + + // Assert + Assert.Equal(expected, body.Trim()); + } + public static IEnumerable RazorViewEngine_RendersPartialViewsData { get diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultViewLocationCacheTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultViewLocationCacheTest.cs index cfacd70e0a..b3c80e2aa0 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultViewLocationCacheTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/DefaultViewLocationCacheTest.cs @@ -15,10 +15,12 @@ namespace Microsoft.AspNet.Mvc.Razor { get { - yield return new[] { new ViewLocationExpanderContext(GetActionContext(), "test") }; + yield return new[] { new ViewLocationExpanderContext(GetActionContext(), "test", isPartial: false) }; + yield return new[] { new ViewLocationExpanderContext(GetActionContext(), "test", isPartial: true) }; var areaActionContext = GetActionContext("controller2", "myarea"); - yield return new[] { new ViewLocationExpanderContext(areaActionContext, "test2") }; + yield return new[] { new ViewLocationExpanderContext(areaActionContext, "test2", isPartial: false) }; + yield return new[] { new ViewLocationExpanderContext(areaActionContext, "test2", isPartial: true) }; var actionContext = GetActionContext("controller3", "area3"); var values = new Dictionary(StringComparer.Ordinal) @@ -26,11 +28,16 @@ namespace Microsoft.AspNet.Mvc.Razor { "culture", "fr" }, { "theme", "sleek" } }; - var expanderContext = new ViewLocationExpanderContext(actionContext, "test3") + var expanderContext = new ViewLocationExpanderContext(actionContext, "test3", isPartial: false) { Values = values }; + yield return new[] { expanderContext }; + expanderContext = new ViewLocationExpanderContext(actionContext, "test3", isPartial: true) + { + Values = values + }; yield return new[] { expanderContext }; } } @@ -71,15 +78,26 @@ namespace Microsoft.AspNet.Mvc.Razor { yield return new object[] { - new ViewLocationExpanderContext(GetActionContext(), "test"), - "test:mycontroller" + new ViewLocationExpanderContext(GetActionContext(), "test", isPartial: false), + "test:0:mycontroller" + }; + + yield return new object[] + { + new ViewLocationExpanderContext(GetActionContext(), "test", isPartial: true), + "test:1:mycontroller" }; var areaActionContext = GetActionContext("controller2", "myarea"); yield return new object[] { - new ViewLocationExpanderContext(areaActionContext, "test2"), - "test2:controller2:myarea" + new ViewLocationExpanderContext(areaActionContext, "test2", isPartial: false), + "test2:0:controller2:myarea" + }; + yield return new object[] + { + new ViewLocationExpanderContext(areaActionContext, "test2", isPartial: true), + "test2:1:controller2:myarea" }; var actionContext = GetActionContext("controller3", "area3"); @@ -88,7 +106,7 @@ namespace Microsoft.AspNet.Mvc.Razor { "culture", "fr" }, { "theme", "sleek" } }; - var expanderContext = new ViewLocationExpanderContext(actionContext, "test3") + var expanderContext = new ViewLocationExpanderContext(actionContext, "test3", isPartial: false) { Values = values }; @@ -96,7 +114,17 @@ namespace Microsoft.AspNet.Mvc.Razor yield return new object[] { expanderContext, - "test3:controller3:area3:culture:fr:theme:sleek" + "test3:0:controller3:area3:culture:fr:theme:sleek" + }; + + expanderContext = new ViewLocationExpanderContext(actionContext, "test3", isPartial: true) + { + Values = values + }; + yield return new object[] + { + expanderContext, + "test3:1:controller3:area3:culture:fr:theme:sleek" }; } } diff --git a/test/WebSites/RazorWebSite/Controllers/ExpanderViewsController.cs b/test/WebSites/RazorWebSite/Controllers/ExpanderViewsController.cs new file mode 100644 index 0000000000..dd7f848899 --- /dev/null +++ b/test/WebSites/RazorWebSite/Controllers/ExpanderViewsController.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 Microsoft.AspNet.Mvc; + +namespace RazorWebSite.Controllers +{ + public class ExpanderViewsController : Controller + { + // This result discovers the Index.cshtml from /View but the partial is executed from /Shared-Views + public ViewResult Index() + { + return View(); + } + + public PartialViewResult Partial() + { + return PartialView("_ExpanderPartial"); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Services/CustomPartialDirectoryViewLocationExpander.cs b/test/WebSites/RazorWebSite/Services/CustomPartialDirectoryViewLocationExpander.cs new file mode 100644 index 0000000000..d689ba2695 --- /dev/null +++ b/test/WebSites/RazorWebSite/Services/CustomPartialDirectoryViewLocationExpander.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.Collections.Generic; +using Microsoft.AspNet.Mvc.Razor; + +namespace RazorWebSite +{ + public class CustomPartialDirectoryViewLocationExpander : IViewLocationExpander + { + public void PopulateValues(ViewLocationExpanderContext context) + { + } + + public virtual IEnumerable ExpandViewLocations(ViewLocationExpanderContext context, + IEnumerable viewLocations) + { + if (context.IsPartial) + { + return ExpandViewLocationsCore(viewLocations); + } + + return viewLocations; + } + + private IEnumerable ExpandViewLocationsCore(IEnumerable viewLocations) + { + foreach (var location in viewLocations) + { + yield return "/Shared-Views/{1}/{0}.cshtml"; + yield return location; + } + } + } +} \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/Index.cshtml b/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/Index.cshtml new file mode 100644 index 0000000000..9818fe9b53 --- /dev/null +++ b/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/Index.cshtml @@ -0,0 +1,3 @@ +@{ + throw new Exception("This view should not be executed"); +} \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/_ExpanderPartial.cshtml b/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/_ExpanderPartial.cshtml new file mode 100644 index 0000000000..314398b63a --- /dev/null +++ b/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/_ExpanderPartial.cshtml @@ -0,0 +1 @@ +@ViewContext.View.Path \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Startup.cs b/test/WebSites/RazorWebSite/Startup.cs index 02c8d82c90..efb21d19d2 100644 --- a/test/WebSites/RazorWebSite/Startup.cs +++ b/test/WebSites/RazorWebSite/Startup.cs @@ -27,6 +27,7 @@ namespace RazorWebSite var expander = new LanguageViewLocationExpander( context => context.HttpContext.Request.Query["language-expander-value"]); options.ViewLocationExpanders.Add(expander); + options.ViewLocationExpanders.Add(new CustomPartialDirectoryViewLocationExpander()); }); }); diff --git a/test/WebSites/RazorWebSite/Views/ExpanderViews/Index.cshtml b/test/WebSites/RazorWebSite/Views/ExpanderViews/Index.cshtml new file mode 100644 index 0000000000..0178074b3f --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/ExpanderViews/Index.cshtml @@ -0,0 +1 @@ +@Html.Partial("_ExpanderPartial") \ No newline at end of file