diff --git a/src/Microsoft.AspNet.Mvc.Razor/IRazorViewEngine.cs b/src/Microsoft.AspNet.Mvc.Razor/IRazorViewEngine.cs index 6a61b1844e..f2bcbf800d 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/IRazorViewEngine.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/IRazorViewEngine.cs @@ -11,12 +11,38 @@ namespace Microsoft.AspNet.Mvc.Razor public interface IRazorViewEngine : IViewEngine { /// - /// Finds a using the same view discovery semantics used in - /// . + /// Finds the page with the given using view locations and information from the + /// . /// /// The . - /// The name or full path to the view. - /// A result representing the result of locating the . - RazorPageResult FindPage(ActionContext context, string page); + /// The name of the page. + /// Determines if the page being found is a partial. + /// The of locating the page. + /// Page search semantics match . + RazorPageResult FindPage(ActionContext context, string pageName, bool isPartial); + + /// + /// Gets the page with the given , relative to + /// unless is already absolute. + /// + /// The absolute path to the currently-executing page, if any. + /// The path to the page. + /// Determines if the page being found is a partial. + /// The of locating the page. + /// See also . + RazorPageResult GetPage(string executingFilePath, string pagePath, bool isPartial); + + /// + /// Converts the given to be absolute, relative to + /// unless is already absolute. + /// + /// The absolute path to the currently-executing page, if any. + /// The path to the page. + /// + /// The combination of and if + /// is a relative path. The value (unchanged) + /// otherwise. + /// + string MakePathAbsolute(string executingFilePath, string pagePath); } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorPageResult.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorPageResult.cs index 89ee7a66e6..4d98254674 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorPageResult.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorPageResult.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// Initializes a new instance of for a successful discovery. /// - /// The name of the page that was located. + /// The name of the page that was found. /// The located . public RazorPageResult(string name, IRazorPage page) { @@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// /// Initializes a new instance of for an unsuccessful discovery. /// - /// The name of the page that was located. + /// The name of the page that was not found. /// The locations that were searched. public RazorPageResult(string name, IEnumerable searchedLocations) { @@ -56,10 +56,8 @@ namespace Microsoft.AspNet.Mvc.Razor } /// - /// Gets the name of the page being located. + /// Gets the name or the path of the page being located. /// - /// This property maps to the name parameter of - /// . public string Name { get; } /// @@ -69,7 +67,7 @@ namespace Microsoft.AspNet.Mvc.Razor public IRazorPage Page { get; } /// - /// Gets the locations that were searched when could not be located. + /// Gets the locations that were searched when could not be found. /// /// This property is null if the page was found. public IEnumerable SearchedLocations { get; } diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs index 05b58c63ca..acffe82583 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorView.cs @@ -166,10 +166,15 @@ namespace Microsoft.AspNet.Mvc.Razor { var viewStart = ViewStartPages[i]; context.ExecutingFilePath = viewStart.Path; + // Copy the layout value from the previous view start (if any) to the current. viewStart.Layout = layout; + await RenderPageCoreAsync(viewStart, context); - layout = viewStart.Layout; + + // Pass correct absolute path to next layout or the entry page if this view start set Layout to a + // relative path. + layout = _viewEngine.MakePathAbsolute(viewStart.Path, viewStart.Layout); } } finally @@ -177,13 +182,13 @@ namespace Microsoft.AspNet.Mvc.Razor context.ExecutingFilePath = oldFilePath; } - // Copy over interesting properties from the ViewStart page to the entry page. + // Copy the layout value from the view start page(s) (if any) to the entry page. RazorPage.Layout = layout; } private async Task RenderLayoutAsync( ViewContext context, - IBufferedTextWriter bodyWriter) + IBufferedTextWriter bodyWriter) { // A layout page can specify another layout page. We'll need to continue // looking for layout pages until they're no longer specified. @@ -202,7 +207,7 @@ namespace Microsoft.AspNet.Mvc.Razor throw new InvalidOperationException(message); } - var layoutPage = GetLayoutPage(context, previousPage.Layout); + var layoutPage = GetLayoutPage(context, previousPage.Path, previousPage.Layout); if (renderedLayouts.Count > 0 && renderedLayouts.Any(l => string.Equals(l.Path, layoutPage.Path, StringComparison.Ordinal))) @@ -237,13 +242,18 @@ namespace Microsoft.AspNet.Mvc.Razor } } - private IRazorPage GetLayoutPage(ViewContext context, string layoutPath) + private IRazorPage GetLayoutPage(ViewContext context, string executingFilePath, string layoutPath) { - var layoutPageResult = _viewEngine.FindPage(context, layoutPath); + var layoutPageResult = _viewEngine.GetPage(executingFilePath, layoutPath, isPartial: true); if (layoutPageResult.Page == null) { - var locations = Environment.NewLine + - string.Join(Environment.NewLine, layoutPageResult.SearchedLocations); + layoutPageResult = _viewEngine.FindPage(context, layoutPath, isPartial: true); + } + + if (layoutPageResult.Page == null) + { + var locations = + Environment.NewLine + string.Join(Environment.NewLine, layoutPageResult.SearchedLocations); throw new InvalidOperationException(Resources.FormatLayoutCannotBeLocated(layoutPath, locations)); } diff --git a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs index e2cf7fc24d..11cea4997e 100644 --- a/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs +++ b/src/Microsoft.AspNet.Mvc.Razor/RazorViewEngine.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; +using System.Linq; using System.Text.Encodings.Web; using Microsoft.AspNet.Mvc.Routing; using Microsoft.AspNet.Mvc.ViewEngines; @@ -63,7 +64,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// which contains following indexes: /// {0} - Action Name /// {1} - Controller Name - /// The values for these locations are case-sensitive on case-senstive file systems. + /// The values for these locations are case-sensitive on case-sensitive file systems. /// For example, the view for the Test action of HomeController should be located at /// /Views/Home/Test.cshtml. Locations such as /views/home/test.cshtml would not be discovered /// @@ -84,7 +85,7 @@ namespace Microsoft.AspNet.Mvc.Razor /// {0} - Action Name /// {1} - Controller Name /// {2} - Area name - /// The values for these locations are case-sensitive on case-senstive file systems. + /// The values for these locations are case-sensitive on case-sensitive file systems. /// For example, the view for the Test action of HomeController should be located at /// /Views/Home/Test.cshtml. Locations such as /views/home/test.cshtml would not be discovered /// @@ -100,65 +101,6 @@ namespace Microsoft.AspNet.Mvc.Razor /// protected IMemoryCache ViewLookupCache { get; } - /// - public ViewEngineResult FindView(ActionContext context, string viewName) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (string.IsNullOrEmpty(viewName)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewName)); - } - - var pageResult = GetViewLocationCacheResult(context, viewName, isPartial: false); - return CreateViewEngineResult(pageResult, viewName, isPartial: false); - } - - /// - public ViewEngineResult FindPartialView(ActionContext context, string partialViewName) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (string.IsNullOrEmpty(partialViewName)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(partialViewName)); - } - - var pageResult = GetViewLocationCacheResult(context, partialViewName, isPartial: true); - return CreateViewEngineResult(pageResult, partialViewName, isPartial: true); - } - - /// - public RazorPageResult FindPage(ActionContext context, string pageName) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (string.IsNullOrEmpty(pageName)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); - } - - var cacheResult = GetViewLocationCacheResult(context, pageName, isPartial: true); - if (cacheResult.Success) - { - var razorPage = cacheResult.ViewEntry.PageFactory(); - return new RazorPageResult(pageName, razorPage); - } - else - { - return new RazorPageResult(pageName, cacheResult.SearchedLocations); - } - } - /// /// Gets the case-normalized route value for the specified route . /// @@ -232,29 +174,109 @@ namespace Microsoft.AspNet.Mvc.Razor return stringRouteValue; } - private ViewLocationCacheResult GetViewLocationCacheResult( - ActionContext context, - string pageName, - bool isPartial) + /// + public RazorPageResult FindPage(ActionContext context, string pageName, bool isPartial) { - if (IsApplicationRelativePath(pageName)) + if (context == null) { - return LocatePageFromPath(pageName, isPartial); + throw new ArgumentNullException(nameof(context)); + } + + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + if (IsApplicationRelativePath(pageName) || IsRelativePath(pageName)) + { + // A path; not a name this method can handle. + return new RazorPageResult(pageName, Enumerable.Empty()); + } + + var cacheResult = LocatePageFromViewLocations(context, pageName, isPartial); + if (cacheResult.Success) + { + var razorPage = cacheResult.ViewEntry.PageFactory(); + razorPage.IsPartial = isPartial; + return new RazorPageResult(pageName, razorPage); } else { - return LocatePageFromViewLocations(context, pageName, isPartial); + return new RazorPageResult(pageName, cacheResult.SearchedLocations); } } - private ViewLocationCacheResult LocatePageFromPath(string pageName, bool isPartial) + /// + public RazorPageResult GetPage(string executingFilePath, string pagePath, bool isPartial) { - var applicationRelativePath = pageName; - if (!pageName.EndsWith(ViewExtension, StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(pagePath)) { - applicationRelativePath += ViewExtension; + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pagePath)); } + if (!(IsApplicationRelativePath(pagePath) || IsRelativePath(pagePath))) + { + // Not a path this method can handle. + return new RazorPageResult(pagePath, Enumerable.Empty()); + } + + var cacheResult = LocatePageFromPath(executingFilePath, pagePath, isPartial); + if (cacheResult.Success) + { + var razorPage = cacheResult.ViewEntry.PageFactory(); + razorPage.IsPartial = isPartial; + return new RazorPageResult(pagePath, razorPage); + } + else + { + return new RazorPageResult(pagePath, cacheResult.SearchedLocations); + } + } + + /// + public ViewEngineResult FindView(ActionContext context, string viewName, bool isPartial) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (string.IsNullOrEmpty(viewName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewName)); + } + + if (IsApplicationRelativePath(viewName) || IsRelativePath(viewName)) + { + // A path; not a name this method can handle. + return ViewEngineResult.NotFound(viewName, Enumerable.Empty()); + } + + var cacheResult = LocatePageFromViewLocations(context, viewName, isPartial); + return CreateViewEngineResult(cacheResult, viewName, isPartial); + } + + /// + public ViewEngineResult GetView(string executingFilePath, string viewPath, bool isPartial) + { + if (string.IsNullOrEmpty(viewPath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(viewPath)); + } + + if (!(IsApplicationRelativePath(viewPath) || IsRelativePath(viewPath))) + { + // Not a path this method can handle. + return ViewEngineResult.NotFound(viewPath, Enumerable.Empty()); + } + + var cacheResult = LocatePageFromPath(executingFilePath, viewPath, isPartial); + return CreateViewEngineResult(cacheResult, viewPath, isPartial); + } + + private ViewLocationCacheResult LocatePageFromPath(string executingFilePath, string pagePath, bool isPartial) + { + var applicationRelativePath = MakePathAbsolute(executingFilePath, pagePath); var cacheKey = new ViewLocationCacheKey(applicationRelativePath, isPartial); ViewLocationCacheResult cacheResult; if (!ViewLookupCache.TryGetValue(cacheKey, out cacheResult)) @@ -272,7 +294,7 @@ namespace Microsoft.AspNet.Mvc.Razor // No views were found at the specified location. Create a not found result. if (cacheResult == null) { - cacheResult = new ViewLocationCacheResult(new[] { pageName }); + cacheResult = new ViewLocationCacheResult(new[] { applicationRelativePath }); } cacheResult = ViewLookupCache.Set( @@ -327,6 +349,42 @@ namespace Microsoft.AspNet.Mvc.Razor return cacheResult; } + /// + public string MakePathAbsolute(string executingFilePath, string pagePath) + { + if (string.IsNullOrEmpty(pagePath)) + { + // Path is not valid; no change required. + return pagePath; + } + + if (IsApplicationRelativePath(pagePath)) + { + // An absolute path already; no change required. + return pagePath; + } + + if (!IsRelativePath(pagePath)) + { + // A page name; no change required. + return pagePath; + } + + // Given a relative path i.e. not yet application-relative (starting with "~/" or "/"), interpret + // path relative to currently-executing view, if any. + if (string.IsNullOrEmpty(executingFilePath)) + { + // Not yet executing a view. Start in app root. + return "/" + pagePath; + } + + // Get directory name (including final slash) but do not use Path.GetDirectoryName() to preserve path + // normalization. + var index = executingFilePath.LastIndexOf('/'); + Debug.Assert(index >= 0); + return executingFilePath.Substring(0, index + 1) + pagePath; + } + private ViewLocationCacheResult OnCacheMiss( ViewLocationExpanderContext expanderContext, ViewLocationCacheKey cacheKey) @@ -451,7 +509,7 @@ namespace Microsoft.AspNet.Mvc.Razor for (var i = 0; i < viewStarts.Length; i++) { var viewStartItem = result.ViewStartEntries[i]; - viewStarts[i] = result.ViewStartEntries[i].PageFactory(); + viewStarts[i] = viewStartItem.PageFactory(); } var view = new RazorView( @@ -469,5 +527,13 @@ namespace Microsoft.AspNet.Mvc.Razor Debug.Assert(!string.IsNullOrEmpty(name)); return name[0] == '~' || name[0] == '/'; } + + private static bool IsRelativePath(string name) + { + Debug.Assert(!string.IsNullOrEmpty(name)); + + // Though ./ViewName looks like a relative path, framework searches for that view using view locations. + return name.EndsWith(ViewExtension, StringComparison.OrdinalIgnoreCase); + } } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/ViewContext.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/ViewContext.cs index c342295ceb..ec3569eed4 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/ViewContext.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/Rendering/ViewContext.cs @@ -130,6 +130,7 @@ namespace Microsoft.AspNet.Mvc.Rendering ValidationSummaryMessageElement = viewContext.ValidationSummaryMessageElement; ValidationMessageElement = viewContext.ValidationMessageElement; + ExecutingFilePath = viewContext.ExecutingFilePath; View = view; ViewData = viewData; TempData = viewContext.TempData; diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs index 728d7fecc4..3239e763c9 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs @@ -76,18 +76,19 @@ namespace Microsoft.AspNet.Mvc.ViewComponents throw new ArgumentNullException(nameof(context)); } - var viewEngine = ViewEngine ?? ResolveViewEngine(context); + var viewContext = context.ViewContext; var viewData = ViewData ?? context.ViewData; + var viewEngine = ViewEngine ?? ResolveViewEngine(context); var isNullOrEmptyViewName = string.IsNullOrEmpty(ViewName); - string qualifiedViewName; - if (!isNullOrEmptyViewName && - (ViewName[0] == '~' || ViewName[0] == '/')) + ViewEngineResult result = null; + if (!isNullOrEmptyViewName) { - // View name that was passed in is already a rooted path, the view engine will handle this. - qualifiedViewName = ViewName; + // If view name was passed in is already a path, the view engine will handle this. + result = viewEngine.GetView(viewContext.ExecutingFilePath, ViewName, isPartial: true); } - else + + if (result == null || !result.Success) { // This will produce a string like: // @@ -101,42 +102,32 @@ namespace Microsoft.AspNet.Mvc.ViewComponents // // This supports a controller or area providing an override for component views. var viewName = isNullOrEmptyViewName ? DefaultViewName : ViewName; - - qualifiedViewName = string.Format( + var qualifiedViewName = string.Format( CultureInfo.InvariantCulture, ViewPathFormat, context.ViewComponentDescriptor.ShortName, viewName); + + result = viewEngine.FindView(viewContext, qualifiedViewName, isPartial: true); } - var view = FindView(context.ViewContext, viewEngine, qualifiedViewName); - - var childViewContext = new ViewContext( - context.ViewContext, - view, - ViewData ?? context.ViewData, - context.Writer); - + var view = result.EnsureSuccessful().View; using (view as IDisposable) { if (_diagnosticSource == null) { - _diagnosticSource = context.ViewContext.HttpContext.RequestServices.GetRequiredService(); + _diagnosticSource = viewContext.HttpContext.RequestServices.GetRequiredService(); } _diagnosticSource.ViewComponentBeforeViewExecute(context, view); + var childViewContext = new ViewContext(viewContext, view, ViewData ?? context.ViewData, context.Writer); await view.RenderAsync(childViewContext); _diagnosticSource.ViewComponentAfterViewExecute(context, view); } } - private static IView FindView(ActionContext context, IViewEngine viewEngine, string viewName) - { - return viewEngine.FindPartialView(context, viewName).EnsureSuccessful().View; - } - private static IViewEngine ResolveViewEngine(ViewComponentContext context) { return context.ViewContext.HttpContext.RequestServices.GetRequiredService(); diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs index d8f9502a8b..28de971413 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. 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.Extensions.OptionsModel; @@ -11,6 +10,8 @@ namespace Microsoft.AspNet.Mvc.ViewEngines /// public class CompositeViewEngine : ICompositeViewEngine { + private const string ViewExtension = ".cshtml"; + /// /// Initializes a new instance of . /// @@ -24,61 +25,53 @@ namespace Microsoft.AspNet.Mvc.ViewEngines public IReadOnlyList ViewEngines { get; } /// - public ViewEngineResult FindPartialView( - ActionContext context, - string partialViewName) + public ViewEngineResult FindView(ActionContext context, string viewName, bool isPartial) { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (partialViewName == null) - { - throw new ArgumentNullException(nameof(partialViewName)); - } - - return FindView(context, partialViewName, partial: true); - } - - /// - public ViewEngineResult FindView( - ActionContext context, - string viewName) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - if (viewName == null) - { - throw new ArgumentNullException(nameof(viewName)); - } - - return FindView(context, viewName, partial: false); - } - - private ViewEngineResult FindView( - ActionContext context, - string viewName, - bool partial) - { - var searchedLocations = Enumerable.Empty(); + List searchedLocations = null; foreach (var engine in ViewEngines) { - var result = partial ? engine.FindPartialView(context, viewName) : - engine.FindView(context, viewName); - + var result = engine.FindView(context, viewName, isPartial); if (result.Success) { return result; } - searchedLocations = searchedLocations.Concat(result.SearchedLocations); + if (searchedLocations == null) + { + searchedLocations = new List(result.SearchedLocations); + } + else + { + searchedLocations.AddRange(result.SearchedLocations); + } } - return ViewEngineResult.NotFound(viewName, searchedLocations); + return ViewEngineResult.NotFound(viewName, searchedLocations ?? Enumerable.Empty()); + } + + /// + public ViewEngineResult GetView(string executingFilePath, string viewPath, bool isPartial) + { + List searchedLocations = null; + foreach (var engine in ViewEngines) + { + var result = engine.GetView(executingFilePath, viewPath, isPartial); + if (result.Success) + { + return result; + } + + if (searchedLocations == null) + { + searchedLocations = new List(result.SearchedLocations); + } + else + { + searchedLocations.AddRange(result.SearchedLocations); + } + } + + return ViewEngineResult.NotFound(viewPath, searchedLocations ?? Enumerable.Empty()); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/IViewEngine.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/IViewEngine.cs index a12719e993..72811b6312 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/IViewEngine.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewEngines/IViewEngine.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. 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.ViewEngines { /// @@ -10,19 +9,23 @@ namespace Microsoft.AspNet.Mvc.ViewEngines public interface IViewEngine { /// - /// Finds the specified view by using the specified action context. + /// Finds the view with the given using view locations and information from the + /// . /// - /// The action context. - /// The name or full path to the view. - /// A result representing the result of locating the view. - ViewEngineResult FindView(ActionContext context, string viewName); + /// The . + /// The name of the view. + /// Determines if the view being found is a partial. + /// The of locating the view. + ViewEngineResult FindView(ActionContext context, string viewName, bool isPartial); /// - /// Finds the specified partial view by using the specified action context. + /// Gets the view with the given , relative to + /// unless is already absolute. /// - /// The action context. - /// The name or full path to the view. - /// A result representing the result of locating the view. - ViewEngineResult FindPartialView(ActionContext context, string partialViewName); + /// The absolute path to the currently-executing view, if any. + /// The path to the view. + /// Determines if the view being found is a partial. + /// The of locating the view. + ViewEngineResult GetView(string executingFilePath, string viewPath, bool isPartial); } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs index a0b0d78d4e..30f682a39a 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs @@ -510,39 +510,45 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures return RenderPartialCoreAsync(partialViewName, model, viewData, ViewContext.Writer); } - protected virtual IHtmlContent GenerateDisplay(ModelExplorer modelExplorer, - string htmlFieldName, - string templateName, - object additionalViewData) + protected virtual IHtmlContent GenerateDisplay( + ModelExplorer modelExplorer, + string htmlFieldName, + string templateName, + object additionalViewData) { - var templateBuilder = new TemplateBuilder(_viewEngine, - ViewContext, - ViewData, - modelExplorer, - htmlFieldName, - templateName, - readOnly: true, - additionalViewData: additionalViewData); + var templateBuilder = new TemplateBuilder( + _viewEngine, + ViewContext, + ViewData, + modelExplorer, + htmlFieldName, + templateName, + readOnly: true, + additionalViewData: additionalViewData); return templateBuilder.Build(); } - protected virtual async Task RenderPartialCoreAsync(string partialViewName, - object model, - ViewDataDictionary viewData, - TextWriter writer) + protected virtual async Task RenderPartialCoreAsync( + string partialViewName, + object model, + ViewDataDictionary viewData, + TextWriter writer) { if (partialViewName == null) { throw new ArgumentNullException(nameof(partialViewName)); } - // Determine which ViewData we should use to construct a new ViewData - var baseViewData = viewData ?? ViewData; + var viewEngineResult = _viewEngine.GetView( + ViewContext.ExecutingFilePath, + partialViewName, + isPartial: true); + if (!viewEngineResult.Success) + { + viewEngineResult = _viewEngine.FindView(ViewContext, partialViewName, isPartial: true); + } - var newViewData = new ViewDataDictionary(baseViewData, model); - - var viewEngineResult = _viewEngine.FindPartialView(ViewContext, partialViewName); if (!viewEngineResult.Success) { var locations = string.Empty; @@ -559,7 +565,12 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var view = viewEngineResult.View; using (view as IDisposable) { + // Determine which ViewData we should use to construct a new ViewData + var baseViewData = viewData ?? ViewData; + + var newViewData = new ViewDataDictionary(baseViewData, model); var viewContext = new ViewContext(ViewContext, view, newViewData, writer); + await viewEngineResult.View.RenderAsync(viewContext); } } diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs index 99e23f11cb..5a90cf8eec 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs @@ -69,7 +69,12 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var viewEngine = viewResult.ViewEngine ?? ViewEngine; var viewName = viewResult.ViewName ?? actionContext.ActionDescriptor.Name; - var result = viewEngine.FindPartialView(actionContext, viewName); + var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isPartial: true); + if (!result.Success) + { + result = viewEngine.FindView(actionContext, viewName, isPartial: true); + } + if (result.Success) { DiagnosticSource.ViewFound(actionContext, true, viewResult, viewName, result.View); diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs index 3d6c26c1b3..8846fa28c5 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs @@ -109,9 +109,14 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures.Internal foreach (string viewName in GetViewNames()) { - var fullViewName = modeViewPath + "/" + viewName; + var viewEngineResult = _viewEngine.GetView(_viewContext.ExecutingFilePath, viewName, isPartial: true); + if (!viewEngineResult.Success) + { + // Success here is more common than with GetView() but GetView() is less expensive. + var fullViewName = modeViewPath + "/" + viewName; + viewEngineResult = _viewEngine.FindView(_viewContext, fullViewName, isPartial: true); + } - var viewEngineResult = _viewEngine.FindPartialView(_viewContext, fullViewName); if (viewEngineResult.Success) { using (var writer = new StringCollectionTextWriter(_viewContext.Writer.Encoding)) diff --git a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs index ced91f96b9..2dd06ae420 100644 --- a/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs +++ b/src/Microsoft.AspNet.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs @@ -68,7 +68,12 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var viewEngine = viewResult.ViewEngine ?? ViewEngine; var viewName = viewResult.ViewName ?? actionContext.ActionDescriptor.Name; - var result = viewEngine.FindView(actionContext, viewName); + var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isPartial: false); + if (!result.Success) + { + result = viewEngine.FindView(actionContext, viewName, isPartial: false); + } + if (result.Success) { if (DiagnosticSource.IsEnabled("Microsoft.AspNet.Mvc.ViewFound")) diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewLocationSpecificationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewLocationSpecificationTest.cs index 6ff2c9cd13..a656033917 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewLocationSpecificationTest.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/RazorViewLocationSpecificationTest.cs @@ -20,9 +20,9 @@ namespace Microsoft.AspNet.Mvc.FunctionalTests [Theory] [InlineData("LayoutSpecifiedWithPartialPathInViewStart")] - [InlineData("LayoutSpecifiedWithPartialPathInViewStart_ForViewSpecifiedWithAppRelativePath")] + [InlineData("LayoutSpecifiedWithPartialPathInViewStart_ForViewSpecifiedWithRelativePath")] [InlineData("LayoutSpecifiedWithPartialPathInViewStart_ForViewSpecifiedWithPartialName")] - [InlineData("LayoutSpecifiedWithPartialPathInViewStart_ForViewSpecifiedWithAppRelativePathWithExtension")] + [InlineData("LayoutSpecifiedWithPartialPathInViewStart_ForViewSpecifiedWithAppRelativePath")] public async Task PartialLayoutPaths_SpecifiedInViewStarts_GetResolvedByViewEngine(string action) { // Arrange @@ -41,8 +41,8 @@ _ViewStart that specifies partial Layout [Theory] [InlineData("LayoutSpecifiedWithPartialPathInPage")] [InlineData("LayoutSpecifiedWithPartialPathInPageWithPartialPath")] + [InlineData("LayoutSpecifiedWithPartialPathInPageWithRelativePath")] [InlineData("LayoutSpecifiedWithPartialPathInPageWithAppRelativePath")] - [InlineData("LayoutSpecifiedWithPartialPathInPageWithAppRelativePathWithExtension")] public async Task PartialLayoutPaths_SpecifiedInPage_GetResolvedByViewEngine(string actionName) { // Arrange @@ -58,8 +58,8 @@ _ViewStart that specifies partial Layout } [Theory] - [InlineData("LayoutSpecifiedWithNonPartialPath")] - [InlineData("LayoutSpecifiedWithNonPartialPathWithExtension")] + [InlineData("LayoutSpecifiedWithRelativePath")] + [InlineData("LayoutSpecifiedWithAppRelativePath")] public async Task NonPartialLayoutPaths_GetResolvedByViewEngine(string actionName) { // Arrange @@ -76,8 +76,8 @@ _ViewStart that specifies partial Layout [Theory] [InlineData("ViewWithPartial_SpecifiedWithPartialName")] - [InlineData("ViewWithPartial_SpecifiedWithAbsoluteName")] - [InlineData("ViewWithPartial_SpecifiedWithAbsoluteNameAndExtension")] + [InlineData("ViewWithPartial_SpecifiedWithRelativePath")] + [InlineData("ViewWithPartial_SpecifiedWithAppRelativePath")] public async Task PartialsCanBeSpecifiedWithPartialPath(string actionName) { // Arrange diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs index 9b85bf697e..c0a10209fe 100644 --- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs +++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ViewEngineTests.cs @@ -326,6 +326,30 @@ Page Content Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true); } + [Fact] + public async Task RelativePathsWorkAsExpected() + { + // Arrange + var expected = +@" + +/ViewEngine/ViewWithRelativePath +ViewWithRelativePath-content +partial-content +View with relative path title +Component with Relative Path + +WriteLiteral says:Write says:98052WriteLiteral says: + +"; + + // Act + var body = await Client.GetStringAsync("http://localhost/ViewEngine/ViewWithRelativePath"); + + // Assert + Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true); + } + [Fact] public async Task ViewComponentsDoNotExecuteViewStarts() { diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs index 81265e473a..453a11628d 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewEngineTest.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test {"controller", "bar"}, }; - public static IEnumerable InvalidViewNameValues + public static IEnumerable AbsoluteViewPathData { get { @@ -80,43 +80,75 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(_controllerTestContext); // Act & Assert - ExceptionAssert.ThrowsArgumentNullOrEmpty(() => viewEngine.FindView(context, viewName), "viewName"); + ExceptionAssert.ThrowsArgumentNullOrEmpty( + () => viewEngine.FindView(context, viewName, isPartial: false), + "viewName"); } [Theory] - [MemberData(nameof(InvalidViewNameValues))] - public void FindView_WithFullPathReturnsNotFound_WhenPathDoesNotMatchExtension(string viewName) + [MemberData(nameof(AbsoluteViewPathData))] + public void FindView_WithFullPath_ReturnsNotFound(string viewName) { // Arrange - var viewEngine = CreateViewEngine(); + var viewEngine = CreateSuccessfulViewEngine(); var context = GetActionContext(_controllerTestContext); // Act - var result = viewEngine.FindView(context, viewName); + var result = viewEngine.FindView(context, viewName, isPartial: false); // Assert Assert.False(result.Success); } [Theory] - [MemberData(nameof(InvalidViewNameValues))] - public void FindViewFullPathSucceedsWithCshtmlEnding(string viewName) + [MemberData(nameof(AbsoluteViewPathData))] + public void FindView_WithFullPathAndCshtmlEnding_ReturnsNotFound(string viewName) { // Arrange - var viewEngine = CreateViewEngine(); - // Append .cshtml so the viewname is no longer invalid + var viewEngine = CreateSuccessfulViewEngine(); + var context = GetActionContext(_controllerTestContext); viewName += ".cshtml"; + + // Act + var result = viewEngine.FindView(context, viewName, isPartial: false); + + // Assert + Assert.False(result.Success); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FindView_WithRelativePath_ReturnsNotFound(bool isPartial) + { + // Arrange + var viewEngine = CreateSuccessfulViewEngine(); var context = GetActionContext(_controllerTestContext); - // Act & Assert - // If this throws then our test case fails - var result = viewEngine.FindPartialView(context, viewName); + // Act + var result = viewEngine.FindView(context, "View.cshtml", isPartial); + // Assert + Assert.False(result.Success); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetView_WithViewName_ReturnsNotFound(bool isPartial) + { + // Arrange + var viewEngine = CreateSuccessfulViewEngine(); + + // Act + var result = viewEngine.GetView("~/Home/View1.cshtml", "View2", isPartial); + + // Assert Assert.False(result.Success); } [Fact] - public void FindPartialView_ReturnsRazorView_IfLookupWasSuccessful() + public void FindView_IsPartial_ReturnsRazorView_IfLookupWasSuccessful() { // Arrange var pageFactory = new Mock(); @@ -140,7 +172,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(_controllerTestContext); // Act - var result = viewEngine.FindPartialView(context, "test-view"); + var result = viewEngine.FindView(context, "test-view", isPartial: true); // Assert Assert.True(result.Success); @@ -151,7 +183,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test } [Fact] - public void FindPartialView_DoesNotExpireCachedResults_IfViewStartsExpire() + public void FindView_IsPartial_DoesNotExpireCachedResults_IfViewStartsExpire() { // Arrange var pageFactory = new Mock(); @@ -172,7 +204,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(_controllerTestContext); // Act - 1 - var result1 = viewEngine.FindPartialView(context, "test-view"); + var result1 = viewEngine.FindView(context, "test-view", isPartial: true); // Assert - 1 Assert.True(result1.Success); @@ -183,7 +215,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test // Act - 2 cancellationTokenSource.Cancel(); - var result2 = viewEngine.FindPartialView(context, "test-view"); + var result2 = viewEngine.FindView(context, "test-view", isPartial: true); // Assert - 2 Assert.True(result2.Success); @@ -195,7 +227,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test [Theory] [InlineData(null)] [InlineData("")] - public void FindPartialView_ThrowsIfViewNameIsNullOrEmpty(string partialViewName) + public void FindView_IsPartial_ThrowsIfViewNameIsNullOrEmpty(string partialViewName) { // Arrange var viewEngine = CreateViewEngine(); @@ -203,52 +235,50 @@ namespace Microsoft.AspNet.Mvc.Razor.Test // Act & Assert ExceptionAssert.ThrowsArgumentNullOrEmpty( - () => viewEngine.FindPartialView(context, partialViewName), - "partialViewName"); + () => viewEngine.FindView(context, partialViewName, isPartial: true), + "viewName"); } [Theory] - [MemberData(nameof(InvalidViewNameValues))] - public void FindPartialView_WithFullPathReturnsNotFound_WhenPathDoesNotMatchExtension(string partialViewName) + [MemberData(nameof(AbsoluteViewPathData))] + public void FindView_IsPartialWithFullPath_ReturnsNotFound(string partialViewName) { // Arrange - var viewEngine = CreateViewEngine(); + var viewEngine = CreateSuccessfulViewEngine(); var context = GetActionContext(_controllerTestContext); // Act - var result = viewEngine.FindPartialView(context, partialViewName); + var result = viewEngine.FindView(context, partialViewName, isPartial: true); // Assert Assert.False(result.Success); } [Theory] - [MemberData(nameof(InvalidViewNameValues))] - public void FindPartialViewFullPathSucceedsWithCshtmlEnding(string partialViewName) + [MemberData(nameof(AbsoluteViewPathData))] + public void FindView_IsPartialWithFullPathAndCshtmlEnding_ReturnsNotFound(string partialViewName) { // Arrange - var viewEngine = CreateViewEngine(); - // Append .cshtml so the viewname is no longer invalid - partialViewName += ".cshtml"; + var viewEngine = CreateSuccessfulViewEngine(); var context = GetActionContext(_controllerTestContext); + partialViewName += ".cshtml"; - // Act & Assert - // If this throws then our test case fails - var result = viewEngine.FindPartialView(context, partialViewName); + // Act + var result = viewEngine.FindView(context, partialViewName, isPartial: true); + // Assert Assert.False(result.Success); } [Fact] - public void FindPartialViewFailureSearchesCorrectLocationsWithAreas() + public void FindView_IsPartial_FailsButSearchesCorrectLocations_WithAreas() { // Arrange - var searchedLocations = new List(); var viewEngine = CreateViewEngine(); var context = GetActionContext(_areaTestContext); // Act - var result = viewEngine.FindPartialView(context, "partial"); + var result = viewEngine.FindView(context, "partial", isPartial: true); // Assert Assert.False(result.Success); @@ -261,14 +291,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Test } [Fact] - public void FindPartialViewFailureSearchesCorrectLocationsWithoutAreas() + public void FindView_IsPartial_FailsButSearchesCorrectLocations_WithoutAreas() { // Arrange var viewEngine = CreateViewEngine(); var context = GetActionContext(_controllerTestContext); // Act - var result = viewEngine.FindPartialView(context, "partialNoArea"); + var result = viewEngine.FindView(context, "partialNoArea", isPartial: true); // Assert Assert.False(result.Success); @@ -279,14 +309,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Test } [Fact] - public void FindViewFailureSearchesCorrectLocationsWithAreas() + public void FindView_FailsButSearchesCorrectLocationsWithAreas() { // Arrange var viewEngine = CreateViewEngine(); var context = GetActionContext(_areaTestContext); // Act - var result = viewEngine.FindView(context, "full"); + var result = viewEngine.FindView(context, "full", isPartial: false); // Assert Assert.False(result.Success); @@ -298,14 +328,14 @@ namespace Microsoft.AspNet.Mvc.Razor.Test } [Fact] - public void FindViewFailureSearchesCorrectLocationsWithoutAreas() + public void FindView_FailsButSearchesCorrectLocationsWithoutAreas() { // Arrange var viewEngine = CreateViewEngine(); var context = GetActionContext(_controllerTestContext); // Act - var result = viewEngine.FindView(context, "fullNoArea"); + var result = viewEngine.FindView(context, "fullNoArea", isPartial: false); // Assert Assert.False(result.Success); @@ -340,7 +370,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(_controllerTestContext); // Act - var result = viewEngine.FindView(context, "test-view"); + var result = viewEngine.FindView(context, "test-view", isPartial: false); // Assert Assert.True(result.Success); @@ -371,7 +401,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(_controllerTestContext); // Act - var result = viewEngine.FindView(context, "test-view"); + var result = viewEngine.FindView(context, "test-view", isPartial: false); // Assert pageFactory.Verify(); @@ -383,10 +413,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Test public void FindView_UsesAreaViewLocationFormat_IfRouteContainsArea() { // Arrange + var viewName = "test-view2"; + var expectedViewName = "fake-area-path/foo/bar/test-view2.rzr"; var pageFactory = new Mock(); var page = Mock.Of(); pageFactory - .Setup(p => p.CreateFactory("fake-area-path/foo/bar/test-view2.rzr")) + .Setup(p => p.CreateFactory(expectedViewName)) .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0])) .Verifiable(); var viewEngine = new TestableRazorViewEngine( @@ -398,9 +430,147 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(_areaTestContext); // Act - var result = viewEngine.FindView(context, "test-view2"); + var result = viewEngine.FindView(context, viewName, isPartial: false); // Assert + Assert.True(result.Success); + Assert.Equal(viewName, result.ViewName); + pageFactory.Verify(); + } + + [Theory] + [InlineData("Test-View.cshtml")] + [InlineData("/Home/Test-View.cshtml")] + public void GetView_DoesNotUseViewLocationFormat_WithRelativePath_IfRouteDoesNotContainArea(string viewName) + { + // Arrange + var expectedViewName = "/Home/Test-View.cshtml"; + var pageFactory = new Mock(); + var page = Mock.Of(); + pageFactory + .Setup(p => p.CreateFactory(expectedViewName)) + .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0])) + .Verifiable(); + var viewEngine = new TestableRazorViewEngine( + pageFactory.Object, + GetOptionsAccessor()); + + // Act + var result = viewEngine.GetView("/Home/Page.cshtml", viewName, isPartial: false); + + // Assert + Assert.True(result.Success); + Assert.Equal(viewName, result.ViewName); + pageFactory.Verify(); + } + + [Theory] + [InlineData("Test-View.cshtml")] + [InlineData("/Home/Test-View.cshtml")] + public void GetView_DoesNotUseViewLocationFormat_WithRelativePath_IfRouteContainArea(string viewName) + { + // Arrange + var expectedViewName = "/Home/Test-View.cshtml"; + var pageFactory = new Mock(); + var page = Mock.Of(); + pageFactory + .Setup(p => p.CreateFactory(expectedViewName)) + .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0])) + .Verifiable(); + var viewEngine = new TestableRazorViewEngine( + pageFactory.Object, + GetOptionsAccessor()); + + // Act + var result = viewEngine.GetView("/Home/Page.cshtml", viewName, isPartial: false); + + // Assert + Assert.True(result.Success); + Assert.Equal(viewName, result.ViewName); + pageFactory.Verify(); + } + + [Theory] + [InlineData("/Test-View.cshtml")] + [InlineData("~/Test-View.CSHTML")] + [InlineData("/Home/Test-View.CSHTML")] + [InlineData("~/Home/Test-View.cshtml")] + [InlineData("~/SHARED/TEST-VIEW.CSHTML")] + public void GetView_UsesGivenPath_WithAppRelativePath(string viewName) + { + // Arrange + var pageFactory = new Mock(); + var page = Mock.Of(); + pageFactory + .Setup(p => p.CreateFactory(viewName)) + .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0])) + .Verifiable(); + var viewEngine = new TestableRazorViewEngine( + pageFactory.Object, + GetOptionsAccessor()); + + // Act + var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isPartial: false); + + // Assert + Assert.True(result.Success); + Assert.Equal(viewName, result.ViewName); + pageFactory.Verify(); + } + + [Theory] + [InlineData("Test-View.cshtml")] + [InlineData("Test-View.CSHTML")] + [InlineData("PATH/TEST-VIEW.CSHTML")] + [InlineData("Path1/Path2/Test-View.cshtml")] + public void GetView_ResolvesRelativeToCurrentPage_WithRelativePath(string viewName) + { + // Arrange + var expectedViewName = $"/Home/{ viewName }"; + var pageFactory = new Mock(); + var page = Mock.Of(); + pageFactory + .Setup(p => p.CreateFactory(expectedViewName)) + .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0])) + .Verifiable(); + var viewEngine = new TestableRazorViewEngine( + pageFactory.Object, + GetOptionsAccessor()); + + // Act + var result = viewEngine.GetView("/Home/Page.cshtml", viewName, isPartial: false); + + // Assert + Assert.True(result.Success); + Assert.Equal(viewName, result.ViewName); + pageFactory.Verify(); + } + + [Theory] + [InlineData("Test-View.cshtml")] + [InlineData("Test-View.CSHTML")] + [InlineData("PATH/TEST-VIEW.CSHTML")] + [InlineData("Path1/Path2/Test-View.cshtml")] + public void GetView_ResolvesRelativeToAppRoot_WithRelativePath_IfNoPageExecuting(string viewName) + { + // Arrange + var expectedViewName = $"/{ viewName }"; + var pageFactory = new Mock(); + var page = Mock.Of(); + pageFactory + .Setup(p => p.CreateFactory(expectedViewName)) + .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0])) + .Verifiable(); + var viewEngine = new TestableRazorViewEngine( + pageFactory.Object, + GetOptionsAccessor()); + + // Act + var result = viewEngine.GetView(executingFilePath: null, viewPath: viewName, isPartial: false); + + // Assert + Assert.True(result.Success); + Assert.Equal(viewName, result.ViewName); pageFactory.Verify(); var view = Assert.IsType(result.View); Assert.Same(page, view.RazorPage); @@ -459,7 +629,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(routeValues); // Act - var result = viewEngine.FindView(context, "test-view"); + var result = viewEngine.FindView(context, "test-view", isPartial: false); // Assert Assert.True(result.Success); @@ -488,7 +658,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(_controllerTestContext); // Act 1 - var result1 = viewEngine.FindView(context, "baz"); + var result1 = viewEngine.FindView(context, "baz", isPartial: false); // Assert 1 Assert.True(result1.Success); @@ -501,7 +671,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test .Setup(p => p.CreateFactory(It.IsAny())) .Throws(new Exception("Shouldn't be called")); - var result2 = viewEngine.FindView(context, "baz"); + var result2 = viewEngine.FindView(context, "baz", isPartial: false); // Assert 2 Assert.True(result2.Success); @@ -539,7 +709,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(_controllerTestContext); // Act 1 - var result1 = viewEngine.FindView(context, "baz"); + var result1 = viewEngine.FindView(context, "baz", isPartial: false); // Assert 1 Assert.True(result1.Success); @@ -548,7 +718,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test // Act 2 cancellationTokenSource.Cancel(); - var result2 = viewEngine.FindView(context, "baz"); + var result2 = viewEngine.FindView(context, "baz", isPartial: false); // Assert 2 Assert.True(result2.Success); @@ -591,7 +761,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(_controllerTestContext); // Act 1 - var result1 = viewEngine.FindView(context, "baz"); + var result1 = viewEngine.FindView(context, "baz", isPartial: false); // Assert 1 Assert.True(result1.Success); @@ -601,7 +771,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test // Act 2 cancellationTokenSource.Cancel(); - var result2 = viewEngine.FindView(context, "baz"); + var result2 = viewEngine.FindView(context, "baz", isPartial: false); // Assert 2 Assert.True(result2.Success); @@ -646,7 +816,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(_controllerTestContext); // Act - 1 - var result = viewEngine.FindView(context, "myview"); + var result = viewEngine.FindView(context, "myview", isPartial: false); // Assert - 1 Assert.False(result.Success); @@ -654,7 +824,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test expander.Verify(); // Act - 2 - result = viewEngine.FindView(context, "myview"); + result = viewEngine.FindView(context, "myview", isPartial: false); // Assert - 2 Assert.False(result.Success); @@ -705,7 +875,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(_controllerTestContext); // Act - 1 - var result = viewEngine.FindView(context, "MyView"); + var result = viewEngine.FindView(context, "MyView", isPartial: false); // Assert - 1 Assert.False(result.Success); @@ -717,7 +887,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test .Setup(p => p.CreateFactory("viewlocation3")) .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0])); cancellationTokenSource.Cancel(); - result = viewEngine.FindView(context, "MyView"); + result = viewEngine.FindView(context, "MyView", isPartial: false); // Assert - 2 Assert.True(result.Success); @@ -731,23 +901,86 @@ namespace Microsoft.AspNet.Mvc.Razor.Test Times.Exactly(2)); } + [Theory] + [MemberData(nameof(AbsoluteViewPathData))] + public void FindPage_WithFullPath_ReturnsNotFound(string viewName) + { + // Arrange + var viewEngine = CreateSuccessfulViewEngine(); + var context = GetActionContext(_controllerTestContext); + + // Act + var result = viewEngine.FindPage(context, viewName, isPartial: false); + + // Assert + Assert.Null(result.Page); + } + + [Theory] + [MemberData(nameof(AbsoluteViewPathData))] + public void FindPage_WithFullPathAndCshtmlEnding_ReturnsNotFound(string viewName) + { + // Arrange + var viewEngine = CreateSuccessfulViewEngine(); + var context = GetActionContext(_controllerTestContext); + viewName += ".cshtml"; + + // Act + var result = viewEngine.FindPage(context, viewName, isPartial: false); + + // Assert + Assert.Null(result.Page); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FindPage_WithRelativePath_ReturnsNotFound(bool isPartial) + { + // Arrange + var viewEngine = CreateSuccessfulViewEngine(); + var context = GetActionContext(_controllerTestContext); + + // Act + var result = viewEngine.FindPage(context, "View.cshtml", isPartial); + + // Assert + Assert.Null(result.Page); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetPage_WithViewName_ReturnsNotFound(bool isPartial) + { + // Arrange + var viewEngine = CreateSuccessfulViewEngine(); + + // Act + var result = viewEngine.GetPage("~/Home/View1.cshtml", "View2", isPartial); + + // Assert + Assert.Null(result.Page); + } + [Theory] [InlineData(null)] [InlineData("")] - public void FindPage_ThrowsIfNameIsNullOrEmpty(string pageName) + public void FindPage_IsPartial_ThrowsIfNameIsNullOrEmpty(string pageName) { // Arrange var viewEngine = CreateViewEngine(); var context = GetActionContext(_controllerTestContext); // Act & Assert - ExceptionAssert.ThrowsArgumentNullOrEmpty(() => viewEngine.FindPage(context, pageName), - "pageName"); + ExceptionAssert.ThrowsArgumentNullOrEmpty( + () => viewEngine.FindPage(context, pageName, isPartial: true), + "pageName"); } [Theory] [MemberData(nameof(ViewLocationExpanderTestData))] - public void FindPage_UsesViewLocationExpander_ToExpandPaths( + public void FindPage_IsPartial_UsesViewLocationExpander_ToExpandPaths( IDictionary routeValues, IEnumerable expectedSeeds) { @@ -789,7 +1022,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test var context = GetActionContext(routeValues); // Act - var result = viewEngine.FindPage(context, "layout"); + var result = viewEngine.FindPage(context, "layout", isPartial: true); // Assert Assert.Equal("layout", result.Name); @@ -799,8 +1032,10 @@ namespace Microsoft.AspNet.Mvc.Razor.Test expander.Verify(); } - [Fact] - public void FindPage_ReturnsSearchedLocationsIfPageCannotBeFound() + [Theory] + [InlineData(false)] + [InlineData(true)] + public void FindPage_ReturnsSearchedLocationsIfPageCannotBeFound(bool isPartial) { // Arrange var expected = new[] @@ -808,13 +1043,12 @@ namespace Microsoft.AspNet.Mvc.Razor.Test "/Views/bar/layout.cshtml", "/Views/Shared/layout.cshtml", }; - var page = Mock.Of(); var viewEngine = CreateViewEngine(); var context = GetActionContext(_controllerTestContext); // Act - var result = viewEngine.FindPage(context, "layout"); + var result = viewEngine.FindPage(context, "layout", isPartial); // Assert Assert.Equal("layout", result.Name); @@ -827,7 +1061,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test [InlineData(true)] // Looks in RouteConstraints [InlineData(false)] - public void FindPage_SelectsActionCaseInsensitively(bool isAttributeRouted) + public void FindPage_IsPartial_SelectsActionCaseInsensitively(bool isAttributeRouted) { // The ActionDescriptor contains "Foo" and the RouteData contains "foo" // which matches the case of the constructor thus searching in the appropriate location. @@ -836,12 +1070,13 @@ namespace Microsoft.AspNet.Mvc.Razor.Test { { "controller", "foo" } }; + var page = new Mock(MockBehavior.Strict); + page.SetupSet(p => p.IsPartial = true); - var page = new Mock(MockBehavior.Strict).Object; var pageFactory = new Mock(); pageFactory .Setup(p => p.CreateFactory("/Views/Foo/details.cshtml")) - .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0])) + .Returns(new RazorPageFactoryResult(() => page.Object, new IChangeToken[0])) .Verifiable(); var viewEngine = CreateViewEngine(pageFactory.Object); @@ -850,14 +1085,17 @@ namespace Microsoft.AspNet.Mvc.Razor.Test { "controller", "Foo" } }; - var context = GetActionContextWithActionDescriptor(routeValues, routesInActionDescriptor, isAttributeRouted); + var context = GetActionContextWithActionDescriptor( + routeValues, + routesInActionDescriptor, + isAttributeRouted); // Act - var result = viewEngine.FindPage(context, "details"); + var result = viewEngine.FindPage(context, "details", isPartial: true); // Assert Assert.Equal("details", result.Name); - Assert.Same(page, result.Page); + Assert.Same(page.Object, result.Page); Assert.Null(result.SearchedLocations); pageFactory.Verify(); } @@ -867,7 +1105,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test [InlineData(true)] // Looks in RouteConstraints [InlineData(false)] - public void FindPage_LooksForPages_UsingActionDescriptor_Controller(bool isAttributeRouted) + public void FindPage_IsPartial_LooksForPages_UsingActionDescriptor_Controller(bool isAttributeRouted) { // Arrange var expected = new[] @@ -884,13 +1122,15 @@ namespace Microsoft.AspNet.Mvc.Razor.Test { { "controller", "bar" } }; - var page = Mock.Of(); var viewEngine = CreateViewEngine(); - var context = GetActionContextWithActionDescriptor(routeValues, routesInActionDescriptor, isAttributeRouted); + var context = GetActionContextWithActionDescriptor( + routeValues, + routesInActionDescriptor, + isAttributeRouted); // Act - var result = viewEngine.FindPage(context, "foo"); + var result = viewEngine.FindPage(context, "foo", isPartial: true); // Assert Assert.Equal("foo", result.Name); @@ -903,7 +1143,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test [InlineData(true)] // Looks in RouteConstraints [InlineData(false)] - public void FindPage_LooksForPages_UsingActionDescriptor_Areas(bool isAttributeRouted) + public void FindPage_IsPartial_LooksForPages_UsingActionDescriptor_Areas(bool isAttributeRouted) { // Arrange var expected = new[] @@ -923,13 +1163,15 @@ namespace Microsoft.AspNet.Mvc.Razor.Test { "controller", "bar" }, { "area", "world" } }; - var page = Mock.Of(); var viewEngine = CreateViewEngine(); - var context = GetActionContextWithActionDescriptor(routeValues, routesInActionDescriptor, isAttributeRouted); + var context = GetActionContextWithActionDescriptor( + routeValues, + routesInActionDescriptor, + isAttributeRouted); // Act - var result = viewEngine.FindPage(context, "foo"); + var result = viewEngine.FindPage(context, "foo", isPartial: true); // Assert Assert.Equal("foo", result.Name); @@ -940,7 +1182,7 @@ namespace Microsoft.AspNet.Mvc.Razor.Test [Theory] [InlineData(true)] [InlineData(false)] - public void FindPage_LooksForPages_UsesRouteValuesAsFallback(bool isAttributeRouted) + public void FindPage_IsPartial_LooksForPages_UsesRouteValuesAsFallback(bool isAttributeRouted) { // Arrange var expected = new[] @@ -953,13 +1195,15 @@ namespace Microsoft.AspNet.Mvc.Razor.Test { { "controller", "foo" } }; - var page = Mock.Of(); var viewEngine = CreateViewEngine(); - var context = GetActionContextWithActionDescriptor(routeValues, new Dictionary(), isAttributeRouted); + var context = GetActionContextWithActionDescriptor( + routeValues, + new Dictionary(), + isAttributeRouted); // Act - var result = viewEngine.FindPage(context, "bar"); + var result = viewEngine.FindPage(context, "bar", isPartial: true); // Assert Assert.Equal("bar", result.Name); @@ -967,6 +1211,166 @@ namespace Microsoft.AspNet.Mvc.Razor.Test Assert.Equal(expected, result.SearchedLocations); } + [Theory] + [InlineData("/Test-View.cshtml")] + [InlineData("~/Test-View.CSHTML")] + [InlineData("/Home/Test-View.CSHTML")] + [InlineData("~/Home/Test-View.cshtml")] + [InlineData("~/SHARED/TEST-VIEW.CSHTML")] + public void GetPage_UsesGivenPath_WithAppRelativePath(string pageName) + { + // Arrange + var pageFactory = new Mock(); + var page = Mock.Of(); + pageFactory + .Setup(p => p.CreateFactory(pageName)) + .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0])) + .Verifiable(); + var viewEngine = new TestableRazorViewEngine( + pageFactory.Object, + GetOptionsAccessor()); + + // Act + var result = viewEngine.GetPage("~/Another/Place.cshtml", pagePath: pageName, isPartial: false); + + // Assert + Assert.Same(page, result.Page); + Assert.Equal(pageName, result.Name); + pageFactory.Verify(); + } + + [Theory] + [InlineData("Test-View.cshtml")] + [InlineData("Test-View.CSHTML")] + [InlineData("PATH/TEST-VIEW.CSHTML")] + [InlineData("Path1/Path2/Test-View.cshtml")] + public void GetPage_ResolvesRelativeToCurrentPage_WithRelativePath(string pageName) + { + // Arrange + var expectedPageName = $"/Home/{ pageName }"; + var pageFactory = new Mock(); + var page = Mock.Of(); + pageFactory + .Setup(p => p.CreateFactory(expectedPageName)) + .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0])) + .Verifiable(); + var viewEngine = new TestableRazorViewEngine( + pageFactory.Object, + GetOptionsAccessor()); + + // Act + var result = viewEngine.GetPage("/Home/Page.cshtml", pageName, isPartial: false); + + // Assert + Assert.Same(page, result.Page); + Assert.Equal(pageName, result.Name); + pageFactory.Verify(); + } + + [Theory] + [InlineData("Test-View.cshtml")] + [InlineData("Test-View.CSHTML")] + [InlineData("PATH/TEST-VIEW.CSHTML")] + [InlineData("Path1/Path2/Test-View.cshtml")] + public void GetPage_ResolvesRelativeToAppRoot_WithRelativePath_IfNoPageExecuting(string pageName) + { + // Arrange + var expectedPageName = $"/{ pageName }"; + var pageFactory = new Mock(); + var page = Mock.Of(); + pageFactory + .Setup(p => p.CreateFactory(expectedPageName)) + .Returns(new RazorPageFactoryResult(() => page, new IChangeToken[0])) + .Verifiable(); + var viewEngine = new TestableRazorViewEngine( + pageFactory.Object, + GetOptionsAccessor()); + + // Act + var result = viewEngine.GetPage(executingFilePath: null, pagePath: pageName, isPartial: false); + + // Assert + Assert.Same(page, result.Page); + Assert.Equal(pageName, result.Name); + pageFactory.Verify(); + } + + [Theory] + [InlineData(null, null)] + [InlineData(null, "")] + [InlineData(null, "Page")] + [InlineData(null, "Folder/Page")] + [InlineData(null, "Folder1/Folder2/Page")] + [InlineData("/Home/Index.cshtml", null)] + [InlineData("/Home/Index.cshtml", "")] + [InlineData("/Home/Index.cshtml", "Page")] + [InlineData("/Home/Index.cshtml", "Folder/Page")] + [InlineData("/Home/Index.cshtml", "Folder1/Folder2/Page")] + public void MakePathAbsolute_ReturnsPagePathUnchanged_IfNotAPath(string executingFilePath, string pagePath) + { + // Arrange + var viewEngine = CreateViewEngine(); + + // Act + var result = viewEngine.MakePathAbsolute(executingFilePath, pagePath); + + // Assert + Assert.Same(pagePath, result); + } + + [Theory] + [InlineData(null, "/Page")] + [InlineData(null, "~/Folder/Page.cshtml")] + [InlineData(null, "/Folder1/Folder2/Page.rzr")] + [InlineData("/Home/Index.cshtml", "~/Page")] + [InlineData("/Home/Index.cshtml", "/Folder/Page.cshtml")] + [InlineData("/Home/Index.cshtml", "~/Folder1/Folder2/Page.rzr")] + public void MakePathAbsolute_ReturnsPageUnchanged_IfAppRelative(string executingFilePath, string pagePath) + { + // Arrange + var viewEngine = CreateViewEngine(); + + // Act + var result = viewEngine.MakePathAbsolute(executingFilePath, pagePath); + + // Assert + Assert.Same(pagePath, result); + } + + [Theory] + [InlineData("Page.cshtml")] + [InlineData("Folder/Page.cshtml")] + [InlineData("../../Folder1/Folder2/Page.cshtml")] + public void MakePathAbsolute_ResolvesRelativeToExecutingPage(string pagePath) + { + // Arrange + var expectedPagePath = "/Home/" + pagePath; + var viewEngine = CreateViewEngine(); + + // Act + var result = viewEngine.MakePathAbsolute("/Home/Page.cshtml", pagePath); + + // Assert + Assert.Equal(expectedPagePath, result); + } + + [Theory] + [InlineData("Page.cshtml")] + [InlineData("Folder/Page.cshtml")] + [InlineData("../../Folder1/Folder2/Page.cshtml")] + public void MakePathAbsolute_ResolvesRelativeToAppRoot_IfNoPageExecuting(string pagePath) + { + // Arrange + var expectedPagePath = "/" + pagePath; + var viewEngine = CreateViewEngine(); + + // Act + var result = viewEngine.MakePathAbsolute(executingFilePath: null, pagePath: pagePath); + + // Assert + Assert.Equal(expectedPagePath, result); + } + [Fact] public void AreaViewLocationFormats_ContainsExpectedLocations() { @@ -1245,6 +1649,17 @@ namespace Microsoft.AspNet.Mvc.Razor.Test } } + // Return RazorViewEngine with factories that always successfully create instances. + private RazorViewEngine CreateSuccessfulViewEngine() + { + var pageFactory = new Mock(MockBehavior.Strict); + pageFactory + .Setup(f => f.CreateFactory(It.IsAny())) + .Returns(new RazorPageFactoryResult(() => Mock.Of(), new IChangeToken[0])); + + return CreateViewEngine(pageFactory.Object); + } + private TestableRazorViewEngine CreateViewEngine( IRazorPageFactoryProvider pageFactory = null, IEnumerable expanders = null) @@ -1268,7 +1683,8 @@ namespace Microsoft.AspNet.Mvc.Razor.Test } var optionsAccessor = new Mock>(); - optionsAccessor.SetupGet(v => v.Value) + optionsAccessor + .SetupGet(v => v.Value) .Returns(options); return optionsAccessor.Object; } diff --git a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs index 4491262b9b..0f726466f9 100644 --- a/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs +++ b/test/Microsoft.AspNet.Mvc.Razor.Test/RazorViewTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http.Features; using Microsoft.AspNet.Http.Internal; @@ -79,13 +80,14 @@ namespace Microsoft.AspNet.Mvc.Razor var viewContext = CreateViewContext(view); var expectedWriter = viewContext.Writer; - activator.Setup(a => a.Activate(page, It.IsAny())) - .Callback((IRazorPage p, ViewContext c) => - { - Assert.Same(c, viewContext); - c.ViewData = viewData; - }) - .Verifiable(); + activator + .Setup(a => a.Activate(page, It.IsAny())) + .Callback((IRazorPage p, ViewContext c) => + { + Assert.Same(c, viewContext); + c.ViewData = viewData; + }) + .Verifiable(); // Act await view.RenderAsync(viewContext); @@ -132,8 +134,12 @@ namespace Microsoft.AspNet.Mvc.Razor var activator = Mock.Of(); var viewEngine = new Mock(); - viewEngine.Setup(v => v.FindPage(It.IsAny(), LayoutPath)) - .Returns(new RazorPageResult(LayoutPath, layout)); + viewEngine + .Setup(p => p.MakePathAbsolute("_ViewStart", LayoutPath)) + .Returns(LayoutPath); + viewEngine + .Setup(v => v.GetPage(pagePath, LayoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(LayoutPath, layout)); var view = new RazorView( viewEngine.Object, activator, @@ -158,8 +164,9 @@ namespace Microsoft.AspNet.Mvc.Razor // Arrange var page = new TestableRazorPage(v => { }); var activator = new Mock(); - activator.Setup(a => a.Activate(page, It.IsAny())) - .Verifiable(); + activator + .Setup(a => a.Activate(page, It.IsAny())) + .Verifiable(); var view = new RazorView( Mock.Of(), activator.Object, @@ -181,9 +188,10 @@ namespace Microsoft.AspNet.Mvc.Razor { // Arrange var htmlEncoder = new HtmlTestEncoder(); - var expected = string.Join(Environment.NewLine, - "HtmlEncode[[layout-content", - "]]HtmlEncode[[page-content]]"); + var expected = string.Join( + Environment.NewLine, + "HtmlEncode[[layout-content", + "]]HtmlEncode[[page-content]]"); var page = new TestableRazorPage(v => { v.HtmlEncoder = htmlEncoder; @@ -202,9 +210,10 @@ namespace Microsoft.AspNet.Mvc.Razor .Setup(p => p.CreateFactory(LayoutPath)) .Returns(new RazorPageFactoryResult(() => layout, new IChangeToken[0])); - var viewEngine = new Mock(); - viewEngine.Setup(v => v.FindPage(It.IsAny(), LayoutPath)) - .Returns(new RazorPageResult(LayoutPath, layout)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetPage(/*executingFilePath*/ null, LayoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(LayoutPath, layout)); var view = new RazorView( viewEngine.Object, @@ -283,8 +292,9 @@ namespace Microsoft.AspNet.Mvc.Razor v.WriteLiteral("Hello world"); }); var activator = new Mock(); - activator.Setup(a => a.Activate(page, It.IsAny())) - .Verifiable(); + activator + .Setup(a => a.Activate(page, It.IsAny())) + .Verifiable(); var view = new RazorView( Mock.Of(), activator.Object, @@ -323,14 +333,26 @@ namespace Microsoft.AspNet.Mvc.Razor v.Layout = null; }); var activator = new Mock(); - activator.Setup(a => a.Activate(viewStart1, It.IsAny())) - .Verifiable(); - activator.Setup(a => a.Activate(viewStart2, It.IsAny())) - .Verifiable(); - activator.Setup(a => a.Activate(page, It.IsAny())) - .Verifiable(); + activator + .Setup(a => a.Activate(viewStart1, It.IsAny())) + .Verifiable(); + activator + .Setup(a => a.Activate(viewStart2, It.IsAny())) + .Verifiable(); + activator + .Setup(a => a.Activate(page, It.IsAny())) + .Verifiable(); + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(engine => engine.MakePathAbsolute(/*executingFilePath*/ null, "/fake-layout-path")) + .Returns("/fake-layout-path"); + viewEngine + .Setup(engine => engine.MakePathAbsolute(/*executingFilePath*/ null, layoutPath)) + .Returns(layoutPath); + var view = new RazorView( - Mock.Of(), + viewEngine.Object, activator.Object, new[] { viewStart1, viewStart2 }, page, @@ -349,11 +371,11 @@ namespace Microsoft.AspNet.Mvc.Razor public async Task RenderAsync_ThrowsIfLayoutPageCannotBeFound() { // Arrange - var expected = string.Join(Environment.NewLine, - "The layout view 'Does-Not-Exist-Layout' could not be located. " + - "The following locations were searched:", - "path1", - "path2"); + var expected = string.Join( + Environment.NewLine, + "The layout view 'Does-Not-Exist-Layout' could not be located. The following locations were searched:", + "path1", + "path2"); var layoutPath = "Does-Not-Exist-Layout"; var page = new TestableRazorPage(v => @@ -361,18 +383,24 @@ namespace Microsoft.AspNet.Mvc.Razor v.Layout = layoutPath; }); - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); var activator = new Mock(); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); - viewEngine.Setup(v => v.FindPage(viewContext, layoutPath)) - .Returns(new RazorPageResult(layoutPath, new[] { "path1", "path2" })) - .Verifiable(); + viewEngine + .Setup(v => v.GetPage(/*executingFilePath*/ null, layoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(layoutPath, Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindPage(viewContext, layoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(layoutPath, new[] { "path1", "path2" })) + .Verifiable(); // Act var ex = await Assert.ThrowsAsync(() => view.RenderAsync(viewContext)); @@ -421,22 +449,26 @@ namespace Microsoft.AspNet.Mvc.Razor v.Write(v.RenderSection("foot")); }); var activator = new Mock(); - activator.Setup(a => a.Activate(page, It.IsAny())) - .Verifiable(); - activator.Setup(a => a.Activate(layout, It.IsAny())) - .Verifiable(); - var viewEngine = new Mock(); + activator + .Setup(a => a.Activate(page, It.IsAny())) + .Verifiable(); + activator + .Setup(a => a.Activate(layout, It.IsAny())) + .Verifiable(); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetPage(/*executingFilePath*/ null, LayoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(LayoutPath, layout)) + .Verifiable(); - var view = new RazorView(viewEngine.Object, - activator.Object, - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + activator.Object, + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); - viewEngine.Setup(p => p.FindPage(viewContext, LayoutPath)) - .Returns(new RazorPageResult(LayoutPath, layout)) - .Verifiable(); // Act await view.RenderAsync(viewContext); @@ -465,16 +497,18 @@ namespace Microsoft.AspNet.Mvc.Razor { Path = LayoutPath }; - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), LayoutPath)) - .Returns(new RazorPageResult(LayoutPath, layout)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetPage(/*executingFilePath*/ null, LayoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(LayoutPath, layout)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act and Assert @@ -526,18 +560,21 @@ namespace Microsoft.AspNet.Mvc.Razor Path = "/Shared/Layout2.cshtml" }; - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "~/Shared/Layout1.cshtml")) - .Returns(new RazorPageResult("~/Shared/Layout1.cshtml", nestedLayout)); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "~/Shared/Layout2.cshtml")) - .Returns(new RazorPageResult("~/Shared/Layout2.cshtml", baseLayout)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetPage(/*executingFilePath*/ null, "~/Shared/Layout1.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout1.cshtml", nestedLayout)); + viewEngine + .Setup(v => v.GetPage("/Shared/Layout1.cshtml", "~/Shared/Layout2.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout2.cshtml", baseLayout)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act @@ -586,18 +623,27 @@ namespace Microsoft.AspNet.Mvc.Razor }); baseLayout.Path = "Layout"; - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "NestedLayout")) - .Returns(new RazorPageResult("NestedLayout", nestedLayout)); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "Layout")) - .Returns(new RazorPageResult("Layout", baseLayout)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetPage(/*executingFilePath*/ null, "NestedLayout", /*isPartial*/ true)) + .Returns(new RazorPageResult("NestedLayout", Enumerable.Empty())); + viewEngine + .Setup(p => p.FindPage(It.IsAny(), "NestedLayout", /*isPartial*/ true)) + .Returns(new RazorPageResult("NestedLayout", nestedLayout)); + viewEngine + .Setup(v => v.GetPage("NestedLayout", "Layout", /*isPartial*/ true)) + .Returns(new RazorPageResult("Layout", Enumerable.Empty())); + viewEngine + .Setup(p => p.FindPage(It.IsAny(), "Layout", /*isPartial*/ true)) + .Returns(new RazorPageResult("Layout", baseLayout)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act @@ -646,18 +692,21 @@ namespace Microsoft.AspNet.Mvc.Razor Path = "/Shared/Layout2.cshtml" }; - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "~/Shared/Layout1.cshtml")) - .Returns(new RazorPageResult("~/Shared/Layout1.cshtml", nestedLayout)); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "~/Shared/Layout2.cshtml")) - .Returns(new RazorPageResult("~/Shared/Layout2.cshtml", baseLayout)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetPage(/*executingFilePath*/ null, "~/Shared/Layout1.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout1.cshtml", nestedLayout)); + viewEngine + .Setup(v => v.GetPage("/Shared/Layout1.cshtml", "~/Shared/Layout2.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout2.cshtml", baseLayout)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act and Assert @@ -711,18 +760,21 @@ namespace Microsoft.AspNet.Mvc.Razor Path = "/Shared/Layout2.cshtml" }; - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "~/Shared/Layout1.cshtml")) - .Returns(new RazorPageResult("~/Shared/Layout1.cshtml", nestedLayout)); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "~/Shared/Layout2.cshtml")) - .Returns(new RazorPageResult("~/Shared/Layout2.cshtml", baseLayout)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(p => p.GetPage("Page", "~/Shared/Layout1.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout1.cshtml", nestedLayout)); + viewEngine + .Setup(p => p.GetPage("/Shared/Layout1.cshtml", "~/Shared/Layout2.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout2.cshtml", baseLayout)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act and Assert @@ -745,16 +797,18 @@ namespace Microsoft.AspNet.Mvc.Razor { Path = LayoutPath }; - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), LayoutPath)) - .Returns(new RazorPageResult(LayoutPath, layout)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(p => p.GetPage(/*executingFilePath*/ null, LayoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(LayoutPath, layout)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act and Assert @@ -807,18 +861,95 @@ namespace Microsoft.AspNet.Mvc.Razor }); layout2.Path = "~/Shared/Layout2.cshtml"; - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "~/Shared/Layout1.cshtml")) - .Returns(new RazorPageResult("~/Shared/Layout1.cshtml", layout1)); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "~/Shared/Layout2.cshtml")) - .Returns(new RazorPageResult("~/Shared/Layout2.cshtml", layout2)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(p => p.GetPage(/*executingFilePath*/ null, "~/Shared/Layout1.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout1.cshtml", layout1)); + viewEngine + .Setup(p => p.GetPage("~/Shared/Layout1.cshtml", "~/Shared/Layout2.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout2.cshtml", layout2)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); + var viewContext = CreateViewContext(view); + + // Act + await view.RenderAsync(viewContext); + + // Assert + Assert.Equal(expected, viewContext.Writer.ToString()); + } + + [Fact] + public async Task RenderAsync_ExecutesNestedLayoutPages_WithRelativePaths() + { + // Arrange + var htmlEncoder = new HtmlTestEncoder(); + var expected = + "HtmlEncode[[layout-2" + Environment.NewLine + + "]]bar-content" + Environment.NewLine + + "HtmlEncode[[layout-1" + Environment.NewLine + + "]]foo-content" + Environment.NewLine + + "body-content"; + + var page = new TestableRazorPage(v => + { + v.HtmlEncoder = htmlEncoder; + v.DefineSection("foo", async writer => + { + await writer.WriteLineAsync("foo-content"); + }); + v.Layout = "Layout1.cshtml"; + v.WriteLiteral("body-content"); + }) + { + Path = "~/Shared/Page.cshtml", + }; + + var layout1 = new TestableRazorPage(v => + { + v.HtmlEncoder = htmlEncoder; + v.Write("layout-1" + Environment.NewLine); + v.Write(v.RenderSection("foo")); + v.DefineSection("bar", writer => writer.WriteLineAsync("bar-content")); + v.RenderBodyPublic(); + v.Layout = "Layout2.cshtml"; + }) + { + Path = "~/Shared/Layout1.cshtml", + }; + + var layout2 = new TestableRazorPage(v => + { + v.HtmlEncoder = htmlEncoder; + v.Write("layout-2" + Environment.NewLine); + v.Write(v.RenderSection("bar")); + v.RenderBodyPublic(); + }) + { + Path = "~/Shared/Layout2.cshtml", + }; + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(p => p.GetPage("~/Shared/Page.cshtml", "Layout1.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout1.cshtml", layout1)); + viewEngine + .Setup(p => p.GetPage("~/Shared/Layout1.cshtml", "Layout2.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout2.cshtml", layout2)); + + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act @@ -845,16 +976,21 @@ namespace Microsoft.AspNet.Mvc.Razor }); layout.Path = "Shared/Layout.cshtml"; - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "_Layout")) - .Returns(new RazorPageResult("_Layout", layout)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(p => p.GetPage(It.IsAny(), "_Layout", /*isPartial*/ true)) + .Returns(new RazorPageResult("_Layout", Enumerable.Empty())); + viewEngine + .Setup(p => p.FindPage(It.IsAny(), "_Layout", /*isPartial*/ true)) + .Returns(new RazorPageResult("_Layout", layout)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act and Assert @@ -888,18 +1024,27 @@ namespace Microsoft.AspNet.Mvc.Razor }); layout2.Path = "/Shared/Layout2.cshtml"; - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "_Layout")) - .Returns(new RazorPageResult("_Layout", layout1)); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "_Layout2")) - .Returns(new RazorPageResult("_Layout2", layout2)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(p => p.GetPage(It.IsAny(), "_Layout", /*isPartial*/ true)) + .Returns(new RazorPageResult("_Layout1", Enumerable.Empty())); + viewEngine + .Setup(p => p.FindPage(It.IsAny(), "_Layout", /*isPartial*/ true)) + .Returns(new RazorPageResult("_Layout", layout1)); + viewEngine + .Setup(p => p.GetPage("Shared/_Layout.cshtml", "_Layout2", /*isPartial*/ true)) + .Returns(new RazorPageResult("_Layout2", Enumerable.Empty())); + viewEngine + .Setup(p => p.FindPage(It.IsAny(), "_Layout2", /*isPartial*/ true)) + .Returns(new RazorPageResult("_Layout2", layout2)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act and Assert @@ -944,7 +1089,7 @@ namespace Microsoft.AspNet.Mvc.Razor await writer.WriteLineAsync(htmlEncoder.Encode(v.RenderSection("foo").ToString())); }); }); - nestedLayout.Path = "~/Shared/Layout2.cshtml"; + nestedLayout.Path = "~/Shared/Layout1.cshtml"; var baseLayout = new TestableRazorPage(v => { @@ -953,20 +1098,23 @@ namespace Microsoft.AspNet.Mvc.Razor v.RenderBodyPublic(); v.Write(v.RenderSection("foo")); }); - baseLayout.Path = "~/Shared/Layout1.cshtml"; + baseLayout.Path = "~/Shared/Layout2.cshtml"; - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "~/Shared/Layout1.cshtml")) - .Returns(new RazorPageResult("~/Shared/Layout1.cshtml", nestedLayout)); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "~/Shared/Layout2.cshtml")) - .Returns(new RazorPageResult("~/Shared/Layout2.cshtml", baseLayout)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(p => p.GetPage(/*executingFilePath*/ null, "~/Shared/Layout1.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout1.cshtml", nestedLayout)); + viewEngine + .Setup(p => p.GetPage("~/Shared/Layout1.cshtml", "~/Shared/Layout2.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("~/Shared/Layout2.cshtml", baseLayout)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act @@ -1010,16 +1158,21 @@ namespace Microsoft.AspNet.Mvc.Razor v.Write(v.RenderSection("foo")); }); - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "layout-1")) - .Returns(new RazorPageResult("layout-1", layout1)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(p => p.GetPage(/*executingFilePath*/ null, "layout-1", /*isPartial*/ true)) + .Returns(new RazorPageResult("layout-1", Enumerable.Empty())); + viewEngine + .Setup(p => p.FindPage(It.IsAny(), "layout-1", /*isPartial*/ true)) + .Returns(new RazorPageResult("layout-1", layout1)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act @@ -1060,16 +1213,21 @@ namespace Microsoft.AspNet.Mvc.Razor v.Write(v.RenderSection("foo")); }); - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "layout-1")) - .Returns(new RazorPageResult("layout-1", layout1)); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(p => p.GetPage(/*executingFilePath*/ null, "layout-1", /*isPartial*/ true)) + .Returns(new RazorPageResult("layout-1", Enumerable.Empty())); + viewEngine + .Setup(p => p.FindPage(It.IsAny(), "layout-1", /*isPartial*/ true)) + .Returns(new RazorPageResult("layout-1", layout1)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act @@ -1094,12 +1252,13 @@ namespace Microsoft.AspNet.Mvc.Razor v.WriteLiteral("after-flush"); }); - var view = new RazorView(Mock.Of(), - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + Mock.Of(), + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act and Assert @@ -1134,17 +1293,19 @@ namespace Microsoft.AspNet.Mvc.Razor v.RenderBodyPublic(); v.Layout = "~/Shared/Layout2.cshtml"; }); - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); var layoutPath = "~/Shared/Layout1.cshtml"; - viewEngine.Setup(p => p.FindPage(It.IsAny(), layoutPath)) - .Returns(new RazorPageResult(layoutPath, layoutPage)); + viewEngine + .Setup(p => p.GetPage("/Views/TestPath/Test.cshtml", layoutPath, /*isPartial*/ true)) + .Returns(new RazorPageResult(layoutPath, layoutPage)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act and Assert @@ -1162,31 +1323,34 @@ namespace Microsoft.AspNet.Mvc.Razor var layoutExecuted = false; var count = -1; var feature = new Mock(MockBehavior.Strict); - feature.Setup(f => f.DecorateWriter(It.IsAny())) - .Returns(() => - { - count++; - if (count == 0) - { - return pageWriter; - } - else if (count == 1) - { - return layoutWriter; - } - throw new Exception(); - }) - .Verifiable(); + feature + .Setup(f => f.DecorateWriter(It.IsAny())) + .Returns(() => + { + count++; + if (count == 0) + { + return pageWriter; + } + else if (count == 1) + { + return layoutWriter; + } + throw new Exception(); + }) + .Verifiable(); var pageContext = Mock.Of(); - feature.Setup(f => f.GetContext("/MyPage.cshtml", pageWriter)) - .Returns(pageContext) - .Verifiable(); + feature + .Setup(f => f.GetContext("/MyPage.cshtml", pageWriter)) + .Returns(pageContext) + .Verifiable(); var layoutContext = Mock.Of(); - feature.Setup(f => f.GetContext("/Layout.cshtml", layoutWriter)) - .Returns(layoutContext) - .Verifiable(); + feature + .Setup(f => f.GetContext("/Layout.cshtml", layoutWriter)) + .Returns(layoutContext) + .Verifiable(); var page = new TestableRazorPage(v => { @@ -1208,15 +1372,21 @@ namespace Microsoft.AspNet.Mvc.Razor }); layout.Path = "/Layout.cshtml"; - var viewEngine = new Mock(); - viewEngine.Setup(p => p.FindPage(It.IsAny(), "Layout")) - .Returns(new RazorPageResult("Layout", layout)); - var view = new RazorView(viewEngine.Object, - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: false); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(p => p.GetPage("/MyPage.cshtml", "Layout", /*isPartial*/ true)) + .Returns(new RazorPageResult("Layout", Enumerable.Empty())); + viewEngine + .Setup(p => p.FindPage(It.IsAny(), "Layout", /*isPartial*/ true)) + .Returns(new RazorPageResult("/Layout.cshtml", layout)); + + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); viewContext.HttpContext.Features.Set(feature.Object); @@ -1256,12 +1426,13 @@ namespace Microsoft.AspNet.Mvc.Razor }); page.Path = "/MyPartialPage.cshtml"; - var view = new RazorView(Mock.Of(), - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: true); + var view = new RazorView( + Mock.Of(), + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: true); var viewContext = CreateViewContext(view); viewContext.Writer = writer; viewContext.HttpContext.Features.Set(feature.Object); @@ -1288,12 +1459,13 @@ namespace Microsoft.AspNet.Mvc.Razor executed = true; }); - var view = new RazorView(Mock.Of(), - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial); + var view = new RazorView + (Mock.Of(), + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial); var viewContext = CreateViewContext(view); // Act @@ -1326,14 +1498,79 @@ namespace Microsoft.AspNet.Mvc.Razor actualViewStart = v.Layout; v.Layout = expectedPage; }); - var viewEngine = Mock.Of(); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(engine => engine.MakePathAbsolute(/*executingFilePath*/ null, expectedViewStart)) + .Returns(expectedViewStart); + viewEngine + .Setup(engine => engine.MakePathAbsolute(/*executingFilePath*/ null, expectedPage)) + .Returns(expectedPage); - var view = new RazorView(viewEngine, - Mock.Of(), - new[] { viewStart1, viewStart2 }, - page, - new HtmlTestEncoder(), - isPartial: false); + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new[] { viewStart1, viewStart2 }, + page, + new HtmlTestEncoder(), + isPartial: false); + var viewContext = CreateViewContext(view); + + // Act + await view.RenderAsync(viewContext); + + // Assert + Assert.Equal(expectedViewStart, actualViewStart); + Assert.Equal(expectedPage, actualPage); + } + + [Fact] + public async Task RenderAsync_CopiesLayoutPropertyFromViewStart_WithRelativePaths() + { + // Arrange + var expectedViewStart = "~/_Layout.cshtml"; + var expectedPage = "~/Home/_Layout.cshtml"; + string actualViewStart = null; + string actualPage = null; + var page = new TestableRazorPage(v => + { + actualPage = v.Layout; + + // Clear it out because we don't care about rendering the layout in this test. + v.Layout = null; + }); + + var viewStart1 = new TestableRazorPage(v => + { + v.Layout = "_Layout.cshtml"; + }) + { + Path = "~/_ViewStart.cshtml", + }; + + var viewStart2 = new TestableRazorPage(v => + { + actualViewStart = v.Layout; + v.Layout = "_Layout.cshtml"; + }) + { + Path = "~/Home/_ViewStart.cshtml", + }; + + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(engine => engine.MakePathAbsolute("~/_ViewStart.cshtml", "_Layout.cshtml")) + .Returns("~/_Layout.cshtml"); + viewEngine + .Setup(engine => engine.MakePathAbsolute("~/Home/_ViewStart.cshtml", "_Layout.cshtml")) + .Returns("~/Home/_Layout.cshtml"); + + var view = new RazorView( + viewEngine.Object, + Mock.Of(), + new[] { viewStart1, viewStart2 }, + page, + new HtmlTestEncoder(), + isPartial: false); var viewContext = CreateViewContext(view); // Act @@ -1364,10 +1601,16 @@ namespace Microsoft.AspNet.Mvc.Razor actual = v.Layout; v.Layout = null; }); - var viewEngine = Mock.Of(); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(engine => engine.MakePathAbsolute(/*executingFilePath*/ null, "Layout")) + .Returns("Layout"); + viewEngine + .Setup(engine => engine.MakePathAbsolute(/*executingFilePath*/ null, /*pagePath*/ null)) + .Returns(null); var view = new RazorView( - viewEngine, + viewEngine.Object, Mock.Of(), new[] { viewStart1, viewStart2 }, page, @@ -1404,10 +1647,13 @@ namespace Microsoft.AspNet.Mvc.Razor isPartialLayout = v.IsPartial; v.RenderBodyPublic(); }); - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(p => p.FindPage(It.IsAny(), "/Layout.cshtml")) - .Returns(new RazorPageResult("Layout", layout)); + .Setup(p => p.MakePathAbsolute(/*executingFilePath*/ null, "/Layout.cshtml")) + .Returns("/Layout.cshtml"); + viewEngine + .Setup(p => p.GetPage(/*executingFilePath*/ null, "/Layout.cshtml", /*isPartial*/ true)) + .Returns(new RazorPageResult("/Layout.cshtml", layout)); var view = new RazorView( viewEngine.Object, @@ -1436,12 +1682,13 @@ namespace Microsoft.AspNet.Mvc.Razor { isPartialPage = v.IsPartial; }); - var view = new RazorView(Mock.Of(), - Mock.Of(), - new IRazorPage[0], - page, - new HtmlTestEncoder(), - isPartial: true); + var view = new RazorView( + Mock.Of(), + Mock.Of(), + new IRazorPage[0], + page, + new HtmlTestEncoder(), + isPartial: true); var viewContext = CreateViewContext(view); // Act diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/PartialViewResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/PartialViewResultTest.cs index a3c1fe5d96..84420f6358 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/PartialViewResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/PartialViewResultTest.cs @@ -4,6 +4,7 @@ #if MOCK_SUPPORT using System; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; @@ -36,9 +37,13 @@ namespace Microsoft.AspNet.Mvc var actionContext = GetActionContext(); - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) + .Setup(v => v.GetView(/*executingFilePath*/ null, "MyView", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("MyView", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "MyView", /*isPartial*/ true)) .Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" })) .Verifiable(); @@ -82,7 +87,11 @@ namespace Microsoft.AspNet.Mvc var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(e => e.FindPartialView(context, "myview")) + .Setup(v => v.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "myview", /*isPartial*/ true)) .Returns(ViewEngineResult.Found("myview", view.Object)) .Verifiable(); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs index f8f449641b..f79d2c9426 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs @@ -7,6 +7,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Globalization; using System.IO; +using System.Linq; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNet.Antiforgery; @@ -315,10 +316,15 @@ namespace Microsoft.AspNet.Mvc.Rendering }) .Returns(Task.FromResult(0)); - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found("MyView", view.Object)); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("MyView", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.Found("MyView", view.Object)) + .Verifiable(); return viewEngine.Object; } diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs index 40c37b12eb..509c819a2d 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs @@ -3,6 +3,7 @@ #if MOCK_SUPPORT using System.IO; +using System.Text; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; using Microsoft.AspNet.Mvc.ModelBinding; @@ -42,6 +43,42 @@ namespace Microsoft.AspNet.Mvc.Rendering Assert.Equal("property", context.ViewBag.Another); Assert.Equal("property", context.ViewData["Another"]); } + + [Fact] + public void CopyConstructor_CopiesExpectedProperties() + { + // Arrange + var originalContext = new ViewContext( + new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()), + view: Mock.Of(), + viewData: new ViewDataDictionary(metadataProvider: new EmptyModelMetadataProvider()), + tempData: new TempDataDictionary(new HttpContextAccessor(), Mock.Of()), + writer: TextWriter.Null, + htmlHelperOptions: new HtmlHelperOptions()); + var view = Mock.Of(); + var viewData = new ViewDataDictionary(originalContext.ViewData); + var writer = new StringCollectionTextWriter(Encoding.UTF8); + + // Act + var context = new ViewContext(originalContext, view, viewData, writer); + + // Assert + Assert.Same(originalContext.ActionDescriptor, context.ActionDescriptor); + Assert.Equal(originalContext.ClientValidationEnabled, context.ClientValidationEnabled); + Assert.Same(originalContext.ExecutingFilePath, context.ExecutingFilePath); + Assert.Same(originalContext.FormContext, context.FormContext); + Assert.Equal(originalContext.Html5DateRenderingMode, context.Html5DateRenderingMode); + Assert.Same(originalContext.HttpContext, context.HttpContext); + Assert.Same(originalContext.ModelState, context.ModelState); + Assert.Same(originalContext.RouteData, context.RouteData); + Assert.Same(originalContext.TempData, context.TempData); + Assert.Same(originalContext.ValidationMessageElement, context.ValidationMessageElement); + Assert.Same(originalContext.ValidationSummaryMessageElement, context.ValidationSummaryMessageElement); + + Assert.Same(view, context.View); + Assert.Same(viewData, context.ViewData); + Assert.Same(writer, context.Writer); + } } } #endif \ No newline at end of file diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs index 312c908430..95c72d64aa 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.IO; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; @@ -35,9 +36,14 @@ namespace Microsoft.AspNet.Mvc .Verifiable(); var viewEngine = new Mock(MockBehavior.Strict); - viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found("some-view", view.Object)) - .Verifiable(); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.Found("Components/Invoke/some-view", view.Object)) + .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); @@ -69,9 +75,10 @@ namespace Microsoft.AspNet.Mvc .Verifiable(); var viewEngine = new Mock(MockBehavior.Strict); - viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found("Default", view.Object)) - .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/Default", /*isPartial*/ true)) + .Returns(ViewEngineResult.Found("Components/Invoke/Default", view.Object)) + .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); @@ -102,9 +109,10 @@ namespace Microsoft.AspNet.Mvc .Verifiable(); var viewEngine = new Mock(MockBehavior.Strict); - viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found("Default", view.Object)) - .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/Default", /*isPartial*/ true)) + .Returns(ViewEngineResult.Found("Components/Invoke/Default", view.Object)) + .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); @@ -139,16 +147,21 @@ namespace Microsoft.AspNet.Mvc { // Arrange var expected = string.Join(Environment.NewLine, - "The view 'Components/Object/some-view' was not found. The following locations were searched:", + "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "location1", "location2."); var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); - viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("Components/Object/some-view", new[] { "location1", "location2" })) - .Verifiable(); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("Components/Invoke/some-view", new[] { "location1", "location2" })) + .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); @@ -179,9 +192,14 @@ namespace Microsoft.AspNet.Mvc .Verifiable(); var viewEngine = new Mock(MockBehavior.Strict); - viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found("some-view", view.Object)) - .Verifiable(); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.Found("Components/Invoke/some-view", view.Object)) + .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); @@ -210,9 +228,14 @@ namespace Microsoft.AspNet.Mvc var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); - viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found("some-view", view)) - .Verifiable(); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.Found("Components/Invoke/some-view", view)) + .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); @@ -240,9 +263,14 @@ namespace Microsoft.AspNet.Mvc var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); - viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found("some-view", view)) - .Verifiable(); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.Found("Components/Invoke/some-view", view)) + .Verifiable(); var serviceProvider = new Mock(); serviceProvider.Setup(p => p.GetService(typeof(ICompositeViewEngine))) @@ -275,18 +303,23 @@ namespace Microsoft.AspNet.Mvc { // Arrange var expected = string.Join(Environment.NewLine, - "The view 'Components/Object/some-view' was not found. The following locations were searched:", + "The view 'Components/Invoke/some-view' was not found. The following locations were searched:", "view-location1", "view-location2."); var view = Mock.Of(); var viewEngine = new Mock(MockBehavior.Strict); - viewEngine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound( - "Components/Object/some-view", - new[] { "view-location1", "view-location2" })) - .Verifiable(); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, "some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("some-view", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "Components/Invoke/some-view", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound( + "Components/Invoke/some-view", + new[] { "view-location1", "view-location2" })) + .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); @@ -338,7 +371,7 @@ namespace Microsoft.AspNet.Mvc [Theory] [InlineData(null)] [InlineData("")] - public void Execute_CallsFindPartialView_WithExpectedPath_WhenViewNameIsNullOrEmpty(string viewName) + public void Execute_CallsFindView_WithIsPartialAndExpectedPath_WhenViewNameIsNullOrEmpty(string viewName) { // Arrange var shortName = "SomeShortName"; @@ -348,8 +381,8 @@ namespace Microsoft.AspNet.Mvc var expectedViewName = $"Components/{shortName}/Default"; var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), expectedViewName)) - .Returns(ViewEngineResult.Found(string.Empty, new Mock().Object)) + .Setup(v => v.FindView(It.IsAny(), expectedViewName, /*isPartial*/ true)) + .Returns(ViewEngineResult.Found(expectedViewName, new Mock().Object)) .Verifiable(); var componentResult = new ViewViewComponentResult(); @@ -367,13 +400,13 @@ namespace Microsoft.AspNet.Mvc [InlineData("~/Home/Index/MyViewComponent1.cshtml")] [InlineData("~MyViewComponent2.cshtml")] [InlineData("/MyViewComponent3.cshtml")] - public void Execute_CallsFindPartialView_WithExpectedPath_WhenViewNameIsSpecified(string viewName) + public void Execute_CallsFindView_WithIsPartialAndExpectedPath_WhenViewNameIsSpecified(string viewName) { // Arrange var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), viewName)) - .Returns(ViewEngineResult.Found(string.Empty, new Mock().Object)) + .Setup(v => v.GetView(/*executingFilePath*/ null, viewName, /*isPartial*/ true)) + .Returns(ViewEngineResult.Found(viewName, new Mock().Object)) .Verifiable(); var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider()); var componentContext = GetViewComponentContext(new Mock().Object, viewData); @@ -417,6 +450,7 @@ namespace Microsoft.AspNet.Mvc var viewComponentDescriptor = new ViewComponentDescriptor() { + ShortName = "Invoke", Type = typeof(object), }; diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs index 11fa3bc52a..5ce060a69b 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs @@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Mvc.ViewEngines var compositeViewEngine = new CompositeViewEngine(optionsAccessor); // Act - var result = compositeViewEngine.FindView(actionContext, viewName); + var result = compositeViewEngine.FindView(actionContext, viewName, isPartial: false); // Assert Assert.False(result.Success); @@ -55,15 +55,16 @@ namespace Microsoft.AspNet.Mvc.ViewEngines { // Arrange var viewName = "test-view"; - var engine = new Mock(); - engine.Setup(e => e.FindView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound(viewName, new[] { "controller/test-view" })); + var engine = new Mock(MockBehavior.Strict); + engine + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound(viewName, new[] { "controller/test-view" })); var optionsAccessor = new TestOptionsManager(); optionsAccessor.Value.ViewEngines.Add(engine.Object); var compositeViewEngine = new CompositeViewEngine(optionsAccessor); // Act - var result = compositeViewEngine.FindView(GetActionContext(), viewName); + var result = compositeViewEngine.FindView(GetActionContext(), viewName, isPartial: false); // Assert Assert.False(result.Success); @@ -75,16 +76,17 @@ namespace Microsoft.AspNet.Mvc.ViewEngines { // Arrange var viewName = "test-view"; - var engine = new Mock(); + var engine = new Mock(MockBehavior.Strict); var view = Mock.Of(); - engine.Setup(e => e.FindView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found(viewName, view)); + engine + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ false)) + .Returns(ViewEngineResult.Found(viewName, view)); var optionsAccessor = new TestOptionsManager(); optionsAccessor.Value.ViewEngines.Add(engine.Object); var compositeViewEngine = new CompositeViewEngine(optionsAccessor); // Act - var result = compositeViewEngine.FindView(GetActionContext(), viewName); + var result = compositeViewEngine.FindView(GetActionContext(), viewName, isPartial: false); // Assert Assert.True(result.Success); @@ -96,17 +98,20 @@ namespace Microsoft.AspNet.Mvc.ViewEngines { // Arrange var viewName = "foo"; - var engine1 = new Mock(); - var engine2 = new Mock(); - var engine3 = new Mock(); + var engine1 = new Mock(MockBehavior.Strict); + var engine2 = new Mock(MockBehavior.Strict); + var engine3 = new Mock(MockBehavior.Strict); var view2 = Mock.Of(); var view3 = Mock.Of(); - engine1.Setup(e => e.FindView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound(viewName, Enumerable.Empty())); - engine2.Setup(e => e.FindView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found(viewName, view2)); - engine3.Setup(e => e.FindView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found(viewName, view3)); + engine1 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound(viewName, Enumerable.Empty())); + engine2 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ false)) + .Returns(ViewEngineResult.Found(viewName, view2)); + engine3 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ false)) + .Returns(ViewEngineResult.Found(viewName, view3)); var optionsAccessor = new TestOptionsManager(); optionsAccessor.Value.ViewEngines.Add(engine1.Object); @@ -115,7 +120,7 @@ namespace Microsoft.AspNet.Mvc.ViewEngines var compositeViewEngine = new CompositeViewEngine(optionsAccessor); // Act - var result = compositeViewEngine.FindView(GetActionContext(), viewName); + var result = compositeViewEngine.FindView(GetActionContext(), viewName, isPartial: false); // Assert Assert.True(result.Success); @@ -128,15 +133,18 @@ namespace Microsoft.AspNet.Mvc.ViewEngines { // Arrange var viewName = "foo"; - var engine1 = new Mock(); - var engine2 = new Mock(); - var engine3 = new Mock(); - engine1.Setup(e => e.FindView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound(viewName, new[] { "1", "2" })); - engine2.Setup(e => e.FindView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound(viewName, new[] { "3" })); - engine3.Setup(e => e.FindView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound(viewName, new[] { "4", "5" })); + var engine1 = new Mock(MockBehavior.Strict); + var engine2 = new Mock(MockBehavior.Strict); + var engine3 = new Mock(MockBehavior.Strict); + engine1 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound(viewName, new[] { "1", "2" })); + engine2 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound(viewName, new[] { "3" })); + engine3 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound(viewName, new[] { "4", "5" })); var optionsAccessor = new TestOptionsManager(); optionsAccessor.Value.ViewEngines.Add(engine1.Object); @@ -145,7 +153,149 @@ namespace Microsoft.AspNet.Mvc.ViewEngines var compositeViewEngine = new CompositeViewEngine(optionsAccessor); // Act - var result = compositeViewEngine.FindView(GetActionContext(), viewName); + var result = compositeViewEngine.FindView(GetActionContext(), viewName, isPartial: false); + + // Assert + Assert.False(result.Success); + Assert.Equal(new[] { "1", "2", "3", "4", "5" }, result.SearchedLocations); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetView_ReturnsNotFoundResult_WhenNoViewEnginesAreRegistered(bool isPartial) + { + // Arrange + var viewName = "test-view.cshtml"; + var optionsAccessor = new TestOptionsManager(); + var compositeViewEngine = new CompositeViewEngine(optionsAccessor); + + // Act + var result = compositeViewEngine.GetView("~/Index.html", viewName, isPartial); + + // Assert + Assert.False(result.Success); + Assert.Empty(result.SearchedLocations); + } + + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetView_ReturnsNotFoundResult_WhenExactlyOneViewEngineIsRegisteredWhichReturnsNotFoundResult( + bool isPartial) + { + // Arrange + var viewName = "test-view.cshtml"; + var expectedViewName = "~/" + viewName; + var engine = new Mock(MockBehavior.Strict); + engine + .Setup(e => e.GetView("~/Index.html", viewName, isPartial)) + .Returns(ViewEngineResult.NotFound(expectedViewName, new[] { expectedViewName })); + var optionsAccessor = new TestOptionsManager(); + optionsAccessor.Value.ViewEngines.Add(engine.Object); + var compositeViewEngine = new CompositeViewEngine(optionsAccessor); + + // Act + var result = compositeViewEngine.GetView("~/Index.html", viewName, isPartial); + + // Assert + Assert.False(result.Success); + Assert.Equal(new[] { expectedViewName }, result.SearchedLocations); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetView_ReturnsView_WhenExactlyOneViewEngineIsRegisteredWhichReturnsAFoundResult(bool isPartial) + { + // Arrange + var viewName = "test-view.cshtml"; + var expectedViewName = "~/" + viewName; + var engine = new Mock(MockBehavior.Strict); + var view = Mock.Of(); + engine + .Setup(e => e.GetView("~/Index.html", viewName, isPartial)) + .Returns(ViewEngineResult.Found(expectedViewName, view)); + var optionsAccessor = new TestOptionsManager(); + optionsAccessor.Value.ViewEngines.Add(engine.Object); + var compositeViewEngine = new CompositeViewEngine(optionsAccessor); + + // Act + var result = compositeViewEngine.GetView("~/Index.html", viewName, isPartial); + + // Assert + Assert.True(result.Success); + Assert.Same(view, result.View); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetView_ReturnsViewFromFirstViewEngineWithFoundResult(bool isPartial) + { + // Arrange + var viewName = "foo.cshtml"; + var expectedViewName = "~/" + viewName; + var engine1 = new Mock(MockBehavior.Strict); + var engine2 = new Mock(MockBehavior.Strict); + var engine3 = new Mock(MockBehavior.Strict); + var view2 = Mock.Of(); + var view3 = Mock.Of(); + engine1 + .Setup(e => e.GetView("~/Index.html", viewName, isPartial)) + .Returns(ViewEngineResult.NotFound(expectedViewName, Enumerable.Empty())); + engine2 + .Setup(e => e.GetView("~/Index.html", viewName, isPartial)) + .Returns(ViewEngineResult.Found(expectedViewName, view2)); + engine3 + .Setup(e => e.GetView("~/Index.html", viewName, isPartial)) + .Returns(ViewEngineResult.Found(expectedViewName, view3)); + + var optionsAccessor = new TestOptionsManager(); + optionsAccessor.Value.ViewEngines.Add(engine1.Object); + optionsAccessor.Value.ViewEngines.Add(engine2.Object); + optionsAccessor.Value.ViewEngines.Add(engine3.Object); + var compositeViewEngine = new CompositeViewEngine(optionsAccessor); + + // Act + var result = compositeViewEngine.GetView("~/Index.html", viewName, isPartial); + + // Assert + Assert.True(result.Success); + Assert.Same(view2, result.View); + Assert.Equal(expectedViewName, result.ViewName); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public void GetView_ReturnsNotFound_IfAllViewEnginesReturnNotFound(bool isPartial) + { + // Arrange + var viewName = "foo.cshtml"; + var expectedViewName = "~/" + viewName; + var engine1 = new Mock(MockBehavior.Strict); + var engine2 = new Mock(MockBehavior.Strict); + var engine3 = new Mock(MockBehavior.Strict); + engine1 + .Setup(e => e.GetView("~/Index.html", viewName, isPartial)) + .Returns(ViewEngineResult.NotFound(expectedViewName, new[] { "1", "2" })); + engine2 + .Setup(e => e.GetView("~/Index.html", viewName, isPartial)) + .Returns(ViewEngineResult.NotFound(expectedViewName, new[] { "3" })); + engine3 + .Setup(e => e.GetView("~/Index.html", viewName, isPartial)) + .Returns(ViewEngineResult.NotFound(expectedViewName, new[] { "4", "5" })); + + var optionsAccessor = new TestOptionsManager(); + optionsAccessor.Value.ViewEngines.Add(engine1.Object); + optionsAccessor.Value.ViewEngines.Add(engine2.Object); + optionsAccessor.Value.ViewEngines.Add(engine3.Object); + var compositeViewEngine = new CompositeViewEngine(optionsAccessor); + + // Act + var result = compositeViewEngine.GetView("~/Index.html", viewName, isPartial); // Assert Assert.False(result.Success); @@ -153,7 +303,7 @@ namespace Microsoft.AspNet.Mvc.ViewEngines } [Fact] - public void FindPartialView_ReturnsNotFoundResult_WhenNoViewEnginesAreRegistered() + public void FindView_IsPartial_ReturnsNotFoundResult_WhenNoViewEnginesAreRegistered() { // Arrange var viewName = "my-partial-view"; @@ -161,7 +311,7 @@ namespace Microsoft.AspNet.Mvc.ViewEngines var compositeViewEngine = new CompositeViewEngine(optionsAccessor); // Act - var result = compositeViewEngine.FindPartialView(GetActionContext(), viewName); + var result = compositeViewEngine.FindView(GetActionContext(), viewName, isPartial: true); // Assert Assert.False(result.Success); @@ -169,19 +319,20 @@ namespace Microsoft.AspNet.Mvc.ViewEngines } [Fact] - public void FindPartialView_ReturnsNotFoundResult_WhenExactlyOneViewEngineIsRegisteredWhichReturnsNotFoundResult() + public void FindView_IsPartial_ReturnsNotFoundResult_WhenExactlyOneViewEngineIsRegisteredWhichReturnsNotFoundResult() { // Arrange var viewName = "partial-view"; - var engine = new Mock(); - engine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound(viewName, new[] { "Shared/partial-view" })); + var engine = new Mock(MockBehavior.Strict); + engine + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(viewName, new[] { "Shared/partial-view" })); var optionsAccessor = new TestOptionsManager(); optionsAccessor.Value.ViewEngines.Add(engine.Object); var compositeViewEngine = new CompositeViewEngine(optionsAccessor); // Act - var result = compositeViewEngine.FindPartialView(GetActionContext(), viewName); + var result = compositeViewEngine.FindView(GetActionContext(), viewName, isPartial: true); // Assert Assert.False(result.Success); @@ -189,20 +340,21 @@ namespace Microsoft.AspNet.Mvc.ViewEngines } [Fact] - public void FindPartialView_ReturnsView_WhenExactlyOneViewEngineIsRegisteredWhichReturnsAFoundResult() + public void FindView_IsPartial_ReturnsView_WhenExactlyOneViewEngineIsRegisteredWhichReturnsAFoundResult() { // Arrange var viewName = "test-view"; - var engine = new Mock(); + var engine = new Mock(MockBehavior.Strict); var view = Mock.Of(); - engine.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found(viewName, view)); + engine + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ true)) + .Returns(ViewEngineResult.Found(viewName, view)); var optionsAccessor = new TestOptionsManager(); optionsAccessor.Value.ViewEngines.Add(engine.Object); var compositeViewEngine = new CompositeViewEngine(optionsAccessor); // Act - var result = compositeViewEngine.FindPartialView(GetActionContext(), viewName); + var result = compositeViewEngine.FindView(GetActionContext(), viewName, isPartial: true); // Assert Assert.True(result.Success); @@ -210,21 +362,24 @@ namespace Microsoft.AspNet.Mvc.ViewEngines } [Fact] - public void FindPartialView_ReturnsViewFromFirstViewEngineWithFoundResult() + public void FindView_IsPartial_ReturnsViewFromFirstViewEngineWithFoundResult() { // Arrange var viewName = "bar"; - var engine1 = new Mock(); - var engine2 = new Mock(); - var engine3 = new Mock(); + var engine1 = new Mock(MockBehavior.Strict); + var engine2 = new Mock(MockBehavior.Strict); + var engine3 = new Mock(MockBehavior.Strict); var view2 = Mock.Of(); var view3 = Mock.Of(); - engine1.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound(viewName, Enumerable.Empty())); - engine2.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found(viewName, view2)); - engine3.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.Found(viewName, view3)); + engine1 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(viewName, Enumerable.Empty())); + engine2 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ true)) + .Returns(ViewEngineResult.Found(viewName, view2)); + engine3 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ true)) + .Returns(ViewEngineResult.Found(viewName, view3)); var optionsAccessor = new TestOptionsManager(); optionsAccessor.Value.ViewEngines.Add(engine1.Object); @@ -233,7 +388,7 @@ namespace Microsoft.AspNet.Mvc.ViewEngines var compositeViewEngine = new CompositeViewEngine(optionsAccessor); // Act - var result = compositeViewEngine.FindPartialView(GetActionContext(), viewName); + var result = compositeViewEngine.FindView(GetActionContext(), viewName, isPartial: true); // Assert Assert.True(result.Success); @@ -242,19 +397,22 @@ namespace Microsoft.AspNet.Mvc.ViewEngines } [Fact] - public void FindPartialView_ReturnsNotFound_IfAllViewEnginesReturnNotFound() + public void FindView_IsPartial_ReturnsNotFound_IfAllViewEnginesReturnNotFound() { // Arrange var viewName = "foo"; - var engine1 = new Mock(); - var engine2 = new Mock(); - var engine3 = new Mock(); - engine1.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound(viewName, new[] { "1", "2" })); - engine2.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound(viewName, new[] { "3" })); - engine3.Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound(viewName, new[] { "4", "5" })); + var engine1 = new Mock(MockBehavior.Strict); + var engine2 = new Mock(MockBehavior.Strict); + var engine3 = new Mock(MockBehavior.Strict); + engine1 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(viewName, new[] { "1", "2" })); + engine2 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(viewName, new[] { "3" })); + engine3 + .Setup(e => e.FindView(It.IsAny(), viewName, /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(viewName, new[] { "4", "5" })); var optionsAccessor = new TestOptionsManager(); optionsAccessor.Value.ViewEngines.Add(engine1.Object); @@ -263,7 +421,7 @@ namespace Microsoft.AspNet.Mvc.ViewEngines var compositeViewEngine = new CompositeViewEngine(optionsAccessor); // Act - var result = compositeViewEngine.FindPartialView(GetActionContext(), viewName); + var result = compositeViewEngine.FindView(GetActionContext(), viewName, isPartial: true); // Assert Assert.False(result.Success); @@ -285,12 +443,12 @@ namespace Microsoft.AspNet.Mvc.ViewEngines public ITestService Service { get; private set; } - public ViewEngineResult FindPartialView(ActionContext context, string partialViewName) + public ViewEngineResult FindView(ActionContext context, string viewName, bool isPartial) { throw new NotImplementedException(); } - public ViewEngineResult FindView(ActionContext context, string viewName) + public ViewEngineResult GetView(string executingFilePath, string viewPath, bool isPartial) { throw new NotImplementedException(); } diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultDisplayTemplatesTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultDisplayTemplatesTest.cs index de4d68c093..9d4352d269 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultDisplayTemplatesTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultDisplayTemplatesTest.cs @@ -128,9 +128,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures "
"+ Environment.NewLine; var model = new DefaultTemplatesUtilities.ObjectWithScaffoldColumn(); - var viewEngine = new Mock(); - viewEngine.Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var htmlHelper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); // Act @@ -259,10 +263,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "Model string" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; @@ -278,10 +285,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "Model string" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; @@ -297,10 +307,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "Model string" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); // Act @@ -317,10 +330,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = propertyValue, }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; @@ -343,9 +359,12 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures { throw new ArgumentException(expectedMessage); })); - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) .Returns(ViewEngineResult.Found("test-view", view.Object)); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; @@ -356,14 +375,15 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures } [Fact] - public void Display_CallsFindPartialView_WithExpectedPath() + public void Display_CallsFindView_WithIsPartialAndExpectedPath() { // Arrange var viewEngine = new Mock(MockBehavior.Strict); - viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), - It.Is(view => view.Equals("DisplayTemplates/String")))) + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "DisplayTemplates/String", /*isPartial*/ true)) .Returns(ViewEngineResult.Found(string.Empty, new Mock().Object)) .Verifiable(); var html = DefaultTemplatesUtilities.GetHtmlHelper(new object(), viewEngine: viewEngine.Object); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultEditorTemplatesTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultEditorTemplatesTest.cs index fbae8c2fdf..575d766ccd 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultEditorTemplatesTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/DefaultEditorTemplatesTest.cs @@ -189,9 +189,13 @@ Environment.NewLine + Environment.NewLine; var model = new DefaultTemplatesUtilities.ObjectWithScaffoldColumn(); - var viewEngine = new Mock(); - viewEngine.Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + var viewEngine = new Mock(MockBehavior.Strict); + viewEngine + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); var htmlHelper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); // Act @@ -354,10 +358,13 @@ Environment.NewLine; { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "True" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper( model, viewEngine.Object, @@ -384,10 +391,13 @@ Environment.NewLine; { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "True" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper( model, viewEngine.Object, @@ -413,10 +423,13 @@ Environment.NewLine; { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "True" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var provider = new TestModelMetadataProvider(); provider.ForProperty("Property1").DisplayDetails(dd => @@ -452,10 +465,13 @@ Environment.NewLine; { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "True" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var provider = new TestModelMetadataProvider(); provider.ForProperty("Property1").DisplayDetails(dd => @@ -490,10 +506,13 @@ Environment.NewLine; { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "True" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var provider = new TestModelMetadataProvider(); provider.ForProperty("Property1").DisplayDetails(dd => @@ -529,10 +548,13 @@ Environment.NewLine; { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "True" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var provider = new TestModelMetadataProvider(); provider.ForProperty("Property1").DisplayDetails(dd => @@ -566,10 +588,13 @@ Environment.NewLine; { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "Model string" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; @@ -597,7 +622,7 @@ Environment.NewLine; ""); var offset = TimeSpan.FromHours(0); @@ -610,10 +635,13 @@ Environment.NewLine; second: 5, millisecond: 6, offset: offset); - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var provider = new TestModelMetadataProvider(); provider.ForType().DisplayDetails(dd => @@ -630,7 +658,7 @@ Environment.NewLine; helper.ViewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix"; // Act - var result = helper.Editor(""); + var result = helper.Editor(string.Empty); // Assert Assert.Equal(expectedInput, HtmlContentUtilities.HtmlContentToString(result)); @@ -650,11 +678,11 @@ Environment.NewLine; ""); // Place DateTime-local value in current timezone. - var offset = string.Equals("", dataTypeName) ? DateTimeOffset.Now.Offset : TimeSpan.FromHours(0); + var offset = string.Equals(string.Empty, dataTypeName) ? DateTimeOffset.Now.Offset : TimeSpan.FromHours(0); var model = new DateTimeOffset( year: 2000, month: 1, @@ -664,10 +692,13 @@ Environment.NewLine; second: 5, millisecond: 60, offset: offset); - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var provider = new TestModelMetadataProvider(); provider.ForType().DisplayDetails(dd => @@ -685,7 +716,7 @@ Environment.NewLine; helper.ViewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix"; // Act - var result = helper.Editor(""); + var result = helper.Editor(string.Empty); // Assert Assert.Equal(expectedInput, HtmlContentUtilities.HtmlContentToString(result)); @@ -708,7 +739,7 @@ Environment.NewLine; ""); var offset = TimeSpan.FromHours(0); @@ -721,11 +752,13 @@ Environment.NewLine; second: 5, millisecond: 60, offset: offset); - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); - + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var provider = new TestModelMetadataProvider(); provider.ForType().DisplayDetails(dd => @@ -745,7 +778,7 @@ Environment.NewLine; helper.ViewData.TemplateInfo.HtmlFieldPrefix = "FieldPrefix"; // Act - var result = helper.Editor(""); + var result = helper.Editor(string.Empty); // Assert Assert.Equal(expectedInput, HtmlContentUtilities.HtmlContentToString(result)); @@ -756,10 +789,13 @@ Environment.NewLine; { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "Model string" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; @@ -777,10 +813,13 @@ Environment.NewLine; { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = "Model string" }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); // Act @@ -799,10 +838,13 @@ Environment.NewLine; { // Arrange var model = new DefaultTemplatesUtilities.ObjectTemplateModel { Property1 = propertyValue, }; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) - .Returns(ViewEngineResult.NotFound("", Enumerable.Empty())); + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; @@ -827,9 +869,12 @@ Environment.NewLine; { throw new FormatException(expectedMessage); })); - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), It.IsAny())) + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) .Returns(ViewEngineResult.Found("test-view", view.Object)); var helper = DefaultTemplatesUtilities.GetHtmlHelper(model, viewEngine.Object); helper.ViewData["Property1"] = "ViewData string"; @@ -840,14 +885,15 @@ Environment.NewLine; } [Fact] - public void EditorForModel_CallsFindPartialView_WithExpectedPath() + public void EditorForModel_CallsFindView_WithIsPartialAndExpectedPath() { // Arrange var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindPartialView(It.IsAny(), - It.Is(view => String.Equals(view, - "EditorTemplates/String")))) + .Setup(v => v.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(string.Empty, Enumerable.Empty())); + viewEngine + .Setup(v => v.FindView(It.IsAny(), "EditorTemplates/String", /*isPartial*/ true)) .Returns(ViewEngineResult.Found(string.Empty, new Mock().Object)) .Verifiable(); var html = DefaultTemplatesUtilities.GetHtmlHelper(new object(), viewEngine: viewEngine.Object); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs index 203b81aa27..adf334a0e4 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs @@ -3,6 +3,7 @@ #if MOCK_SUPPORT using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; @@ -26,9 +27,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var executor = GetViewExecutor(); var viewName = "my-view"; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(e => e.FindPartialView(context, viewName)) + .Setup(e => e.GetView(/*executingFilePath*/ null, viewName, /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound(viewName, Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(e => e.FindView(context, viewName, /*isPartial*/ true)) .Returns(ViewEngineResult.Found(viewName, Mock.Of())) .Verifiable(); @@ -118,7 +123,11 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var viewName = "myview"; var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(e => e.FindPartialView(context, "myview")) + .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ true)) + .Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(e => e.FindView(context, "myview", /*isPartial*/ true)) .Returns(ViewEngineResult.NotFound("myview", new string[] { "location/myview" })); var viewResult = new PartialViewResult @@ -209,8 +218,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(e => e.FindPartialView(It.IsAny(), It.IsAny())) - .Returns((_, name) => ViewEngineResult.Found(name, Mock.Of())); + .Setup(e => e.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ true)) + .Returns( + (executing, name, isPartial) => ViewEngineResult.NotFound(name, Enumerable.Empty())); + viewEngine + .Setup(e => e.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ true)) + .Returns( + (context, name, isPartial) => ViewEngineResult.Found(name, Mock.Of())); var options = new TestOptionsManager(); options.Value.ViewEngines.Add(viewEngine.Object); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs index a929bc1849..4141ab07ab 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs @@ -3,6 +3,7 @@ #if MOCK_SUPPORT using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http.Internal; using Microsoft.AspNet.Mvc.Abstractions; @@ -26,9 +27,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var executor = GetViewExecutor(); var viewName = "my-view"; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(e => e.FindView(context, viewName)) + .Setup(e => e.GetView(/*executingFilePath*/ null, viewName, /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound(viewName, Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(e => e.FindView(context, viewName, /*isPartial*/ false)) .Returns(ViewEngineResult.Found(viewName, Mock.Of())) .Verifiable(); @@ -116,9 +121,12 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var executor = GetViewExecutor(diagnosticSource); var viewName = "myview"; - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(e => e.FindView(context, "myview")) + .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty())); + viewEngine + .Setup(e => e.FindView(context, "myview", /*isPartial*/ false)) .Returns(ViewEngineResult.NotFound("myview", new string[] { "location/myview" })); var viewResult = new ViewResult @@ -209,8 +217,13 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(e => e.FindView(It.IsAny(), It.IsAny())) - .Returns((_, name) => ViewEngineResult.Found(name, Mock.Of())); + .Setup(e => e.GetView(/*executingFilePath*/ null, It.IsAny(), /*isPartial*/ false)) + .Returns( + (path, name, partial) => ViewEngineResult.NotFound(name, Enumerable.Empty())); + viewEngine + .Setup(e => e.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ false)) + .Returns( + (context, name, partial) => ViewEngineResult.Found(name, Mock.Of())); var options = new TestOptionsManager(); options.Value.ViewEngines.Add(viewEngine.Object); diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs index 9920621f05..9b0662d107 100644 --- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs +++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewResultTest.cs @@ -4,6 +4,7 @@ #if MOCK_SUPPORT using System; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using Microsoft.AspNet.Http; using Microsoft.AspNet.Http.Internal; @@ -36,9 +37,13 @@ namespace Microsoft.AspNet.Mvc var actionContext = GetActionContext(); - var viewEngine = new Mock(); + var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(v => v.FindView(It.IsAny(), It.IsAny())) + .Setup(e => e.GetView(/*executingFilePath*/ null, "MyView", /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("MyView", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(v => v.FindView(It.IsAny(), It.IsAny(), /*isPartial*/ false)) .Returns(ViewEngineResult.NotFound("MyView", new[] { "Location1", "Location2" })) .Verifiable(); @@ -81,7 +86,11 @@ namespace Microsoft.AspNet.Mvc var viewEngine = new Mock(MockBehavior.Strict); viewEngine - .Setup(e => e.FindView(context, "myview")) + .Setup(e => e.GetView(/*executingFilePath*/ null, "myview", /*isPartial*/ false)) + .Returns(ViewEngineResult.NotFound("myview", Enumerable.Empty())) + .Verifiable(); + viewEngine + .Setup(e => e.FindView(context, "myview", /*isPartial*/ false)) .Returns(ViewEngineResult.Found("myview", view.Object)) .Verifiable(); diff --git a/test/WebSites/CompositeViewEngineWebSite/TestViewEngine.cs b/test/WebSites/CompositeViewEngineWebSite/TestViewEngine.cs index 62673f2cf5..3615009333 100644 --- a/test/WebSites/CompositeViewEngineWebSite/TestViewEngine.cs +++ b/test/WebSites/CompositeViewEngineWebSite/TestViewEngine.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.Linq; using Microsoft.AspNet.Mvc; using Microsoft.AspNet.Mvc.ViewEngines; @@ -9,22 +10,22 @@ namespace CompositeViewEngineWebSite { public class TestViewEngine : IViewEngine { - public ViewEngineResult FindPartialView(ActionContext context, string partialViewName) + public ViewEngineResult FindView(ActionContext context, string viewName, bool isPartial) { - if (string.Equals(partialViewName, "partial-test-view", StringComparison.Ordinal)) + if (string.Equals(viewName, "partial-test-view", StringComparison.Ordinal) || + string.Equals(viewName, "test-view", StringComparison.Ordinal)) { - return ViewEngineResult.Found(partialViewName, new TestPartialView()); + var view = isPartial ? (IView)new TestPartialView() : new TestView(); + + return ViewEngineResult.Found(viewName, view); } - return ViewEngineResult.NotFound(partialViewName, new[] { partialViewName }); + + return ViewEngineResult.NotFound(viewName, Enumerable.Empty()); } - public ViewEngineResult FindView(ActionContext context, string viewName) + public ViewEngineResult GetView(string executingFilePath, string viewPath, bool isPartial) { - if (string.Equals(viewName, "test-view")) - { - return ViewEngineResult.Found(viewName, new TestView()); - } - return ViewEngineResult.NotFound(viewName, new[] { viewName }); + return ViewEngineResult.NotFound(viewPath, Enumerable.Empty()); } } } \ No newline at end of file diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareController.cs b/test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareController.cs index 803a040b16..5aa67c97cb 100644 --- a/test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareController.cs +++ b/test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareController.cs @@ -22,7 +22,7 @@ namespace ErrorPageMiddlewareWebSite [HttpGet("/ErrorFromViewImports")] public IActionResult ViewImportsError() { - return View("~/Views/ErrorFromViewImports/Index"); + return View("~/Views/ErrorFromViewImports/Index.cshtml"); } } } diff --git a/test/WebSites/PrecompilationWebSite/Controllers/HomeController.cs b/test/WebSites/PrecompilationWebSite/Controllers/HomeController.cs index 3314145d16..acd35e5076 100644 --- a/test/WebSites/PrecompilationWebSite/Controllers/HomeController.cs +++ b/test/WebSites/PrecompilationWebSite/Controllers/HomeController.cs @@ -14,18 +14,18 @@ namespace PrecompilationWebSite.Controllers public IActionResult PrecompiledViewsCanConsumeCompilationOptions() { - return View("~/Views/ViewsConsumingCompilationOptions/Index"); + return View("~/Views/ViewsConsumingCompilationOptions/Index.cshtml"); } public IActionResult GlobalDeletedPriorToFirstRequest() { - return View("~/Views/ViewImportsDelete/Index"); + return View("~/Views/ViewImportsDelete/Index.cshtml"); } [HttpGet("/Test")] public IActionResult TestView() { - return View("~/Views/Test/Index"); + return View("~/Views/Test/Index.cshtml"); } } } diff --git a/test/WebSites/RazorWebSite/Components/ComponentWithRelativePath.cs b/test/WebSites/RazorWebSite/Components/ComponentWithRelativePath.cs new file mode 100644 index 0000000000..614f098caf --- /dev/null +++ b/test/WebSites/RazorWebSite/Components/ComponentWithRelativePath.cs @@ -0,0 +1,15 @@ +// Copyright (c) .NET Foundation. 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.Components +{ + public class ComponentWithRelativePath : ViewComponent + { + public IViewComponentResult Invoke(Person person) + { + return View("../Shared/Components/ComponentWithRelativePath.cshtml", person); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Components/InheritingViewComponent.cs b/test/WebSites/RazorWebSite/Components/InheritingViewComponent.cs index 370d66e711..96d5b1743c 100644 --- a/test/WebSites/RazorWebSite/Components/InheritingViewComponent.cs +++ b/test/WebSites/RazorWebSite/Components/InheritingViewComponent.cs @@ -10,7 +10,7 @@ namespace RazorWebSite.Components { public IViewComponentResult Invoke(Address address) { - return View("/Views/InheritingInherits/_ViewComponent", address); + return View("/Views/InheritingInherits/_ViewComponent.cshtml", address); } } } diff --git a/test/WebSites/RazorWebSite/Controllers/DirectivesController.cs b/test/WebSites/RazorWebSite/Controllers/DirectivesController.cs index 1c9a58a7cc..690e37b6c3 100644 --- a/test/WebSites/RazorWebSite/Controllers/DirectivesController.cs +++ b/test/WebSites/RazorWebSite/Controllers/DirectivesController.cs @@ -29,7 +29,7 @@ namespace RazorWebSite } }; - return View("/Views/InheritingInherits/Index", model); + return View("/Views/InheritingInherits/Index.cshtml", model); } } } \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Controllers/PartialViewEngineController.cs b/test/WebSites/RazorWebSite/Controllers/PartialViewEngineController.cs index 95a2b3eafd..00e37e8e36 100644 --- a/test/WebSites/RazorWebSite/Controllers/PartialViewEngineController.cs +++ b/test/WebSites/RazorWebSite/Controllers/PartialViewEngineController.cs @@ -14,7 +14,7 @@ namespace RazorWebSite.Controllers public IActionResult ViewWithFullPath() { - return PartialView(@"/Views/ViewEngine/ViewWithFullPath.cshtml"); + return PartialView("/Views/ViewEngine/ViewWithFullPath.rzr"); } public IActionResult PartialViewWithNamePassedIn() diff --git a/test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs b/test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs index df10cb264c..2be0a1c989 100644 --- a/test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs +++ b/test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs @@ -15,7 +15,12 @@ namespace RazorWebSite.Controllers public IActionResult ViewWithFullPath() { - return View(@"/Views/ViewEngine/ViewWithFullPath.cshtml"); + return View("/Views/ViewEngine/ViewWithFullPath.rzr"); + } + + public IActionResult ViewWithRelativePath() + { + return View("Views/ViewEngine/ViewWithRelativePath.cshtml"); } public IActionResult ViewWithLayout() @@ -35,6 +40,7 @@ namespace RazorWebSite.Controllers { Address = new Address { ZipCode = "98052" } }; + return View(model); } diff --git a/test/WebSites/RazorWebSite/Controllers/ViewNameSpecification_HomeController.cs b/test/WebSites/RazorWebSite/Controllers/ViewNameSpecification_HomeController.cs index 4065e24cbd..4d331f1488 100644 --- a/test/WebSites/RazorWebSite/Controllers/ViewNameSpecification_HomeController.cs +++ b/test/WebSites/RazorWebSite/Controllers/ViewNameSpecification_HomeController.cs @@ -17,12 +17,12 @@ namespace RazorWebSite.Controllers return View("LayoutSpecifiedWithPartialPathInViewStart"); } - public IActionResult LayoutSpecifiedWithPartialPathInViewStart_ForViewSpecifiedWithAppRelativePath() + public IActionResult LayoutSpecifiedWithPartialPathInViewStart_ForViewSpecifiedWithRelativePath() { - return View("~/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInViewStart"); + return View("Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInViewStart.cshtml"); } - public IActionResult LayoutSpecifiedWithPartialPathInViewStart_ForViewSpecifiedWithAppRelativePathWithExtension() + public IActionResult LayoutSpecifiedWithPartialPathInViewStart_ForViewSpecifiedWithAppRelativePath() { return View("~/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInViewStart.cshtml"); } @@ -37,23 +37,23 @@ namespace RazorWebSite.Controllers return View("LayoutSpecifiedWithPartialPathInPage"); } - public IActionResult LayoutSpecifiedWithPartialPathInPageWithAppRelativePath() + public IActionResult LayoutSpecifiedWithPartialPathInPageWithRelativePath() { - return View("~/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInPage"); + return View("Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInPage.cshtml"); } - public IActionResult LayoutSpecifiedWithPartialPathInPageWithAppRelativePathWithExtension() + public IActionResult LayoutSpecifiedWithPartialPathInPageWithAppRelativePath() { return View("~/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInPage.cshtml"); } - public IActionResult LayoutSpecifiedWithNonPartialPath() + public IActionResult LayoutSpecifiedWithRelativePath() { - ViewData["Layout"] = "~/Views/ViewNameSpecification_Home/_NonSharedLayout"; + ViewData["Layout"] = "_NonSharedLayout.cshtml"; return View("PageWithNonPartialLayoutPath"); } - public IActionResult LayoutSpecifiedWithNonPartialPathWithExtension() + public IActionResult LayoutSpecifiedWithAppRelativePath() { ViewData["Layout"] = "~/Views/ViewNameSpecification_Home/_NonSharedLayout.cshtml"; return View("PageWithNonPartialLayoutPath"); @@ -65,13 +65,13 @@ namespace RazorWebSite.Controllers return View("ViewWithPartials"); } - public IActionResult ViewWithPartial_SpecifiedWithAbsoluteName() + public IActionResult ViewWithPartial_SpecifiedWithRelativePath() { - ViewBag.Partial = "~/Views/ViewNameSpecification_Home/NonSharedPartial"; + ViewBag.Partial = "NonSharedPartial.cshtml"; return View("ViewWithPartials"); } - public IActionResult ViewWithPartial_SpecifiedWithAbsoluteNameAndExtension() + public IActionResult ViewWithPartial_SpecifiedWithAppRelativePath() { ViewBag.Partial = "~/Views/ViewNameSpecification_Home/NonSharedPartial.cshtml"; return View("ViewWithPartials"); diff --git a/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithRelativePath.cshtml b/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithRelativePath.cshtml new file mode 100644 index 0000000000..29c6d4ad34 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithRelativePath.cshtml @@ -0,0 +1,7 @@ +@model Person +@{ + Layout = "../_ComponentLayout.cshtml"; +} +Component with Relative Path +@Html.DisplayFor(model => model.Name, templateName: "~/Views/Shared/DisplayTemplates/Name.cshtml") +@Html.DisplayFor(model => model.Address, templateName: "../../InheritingInherits/_ViewComponent.cshtml") \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/Shared/DisplayTemplates/Name.cshtml b/test/WebSites/RazorWebSite/Views/Shared/DisplayTemplates/Name.cshtml new file mode 100644 index 0000000000..388bd1f959 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/Shared/DisplayTemplates/Name.cshtml @@ -0,0 +1,2 @@ +@model string + \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.cshtml b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.rzr similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.cshtml rename to test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.rzr diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithRelativePath.cshtml b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithRelativePath.cshtml new file mode 100644 index 0000000000..f138e7d744 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithRelativePath.cshtml @@ -0,0 +1,15 @@ +@{ + Layout = "_NestedLayout.cshtml"; + ViewData["Title"] = "View with relative path title"; + var person = new Person + { + Address = new Address + { + ZipCode = "98052", + }, + Name = "Fred", + }; +} +ViewWithRelativePath-content +@await Html.PartialAsync("../Shared/_PartialThatSetsTitle.cshtml") +@await Component.InvokeAsync("ComponentWithRelativePath", person) \ No newline at end of file