在ASP.NET 5上,组件视图必须位于以下两个位置之一:
Views/NameOfControllerUsingComponent/Components/ComponentName/Default.cshtml
Views/Shared/Components/ComponentName/Default.cshtml
有没有办法将其更改为:
Views/NameOfControllerUsingComponent/Components/ComponentName.cshtml
Views/Shared/Components/ComponentName.cshtml
基本上,删除文件夹ComponentName并将视图名称从Default.cshtml更改为ComponentName.cshtml。
对我而言,它更有意义......是否可能?
答案 0 :(得分:4)
仅当您创建一个派生自框架提供的基础ViewComponent
的视图组件时,才会应用该约定。
该类定义View
助手,返回ViewViewComponentResult
:
public ViewViewComponentResult View<TModel>(string viewName, TModel model)
{
var viewData = new ViewDataDictionary<TModel>(ViewData, model);
return new ViewViewComponentResult
{
ViewEngine = ViewEngine,
ViewName = viewName,
ViewData = viewData
};
}
ViewViewComponentResult
是定义约定的地方:
private const string ViewPathFormat = "Components/{0}/{1}";
private const string DefaultViewName = "Default";
public async Task ExecuteAsync(ViewComponentContext context)
{
...
string qualifiedViewName;
if (!isNullOrEmptyViewName &&
(ViewName[0] == '~' || ViewName[0] == '/'))
{
// View name that was passed in is already a rooted path, the view engine will handle this.
qualifiedViewName = ViewName;
}
else
{
// This will produce a string like:
//
// Components/Cart/Default
//
// The view engine will combine this with other path info to search paths like:
//
// Views/Shared/Components/Cart/Default.cshtml
// Views/Home/Components/Cart/Default.cshtml
// Areas/Blog/Views/Shared/Components/Cart/Default.cshtml
//
// This supports a controller or area providing an override for component views.
var viewName = isNullOrEmptyViewName ? DefaultViewName : ViewName;
qualifiedViewName = string.Format(
CultureInfo.InvariantCulture,
ViewPathFormat,
context.ViewComponentDescriptor.ShortName,
viewName);
}
...
}
请注意,如果从视图组件返回视图的完整路径作为视图名称,则视图组件将使用指定的视图。 类似的东西:
return View("~/Views/Shared/Components/ComponentName.cshtml")
由于无法修改ViewViewComponentResult
中的约定,并且您的方法仅适用于具有单个视图的视图组件,您可以使用根视图路径方法构建一些内容:
ViewComponent
课程,扩展现有的课程。添加新的帮助方法或隐藏现有的View
方法,以使用完整路径返回视图:
public ViewViewComponentResult MyView<TModel>(TModel model)
{
var viewName = string.Format(
"~/Views/Shared/Components/{0}.cshtml",
this.ViewComponentContext.ViewComponentDescriptor.ShortName)
return View(viewName, model);
}
ViewComponent
的扩展方法,而不必创建自己的类。另一种方法是创建一个类SingleViewViewComponent
复制ViewComponent
的代码,但替换ViewViewComponentResult View<TModel>(string viewName, TModel model)
的实现。然后,在创建视图组件时,您将继承SingleViewViewComponent
而不是ViewComponent
。
答案 1 :(得分:0)
我花了一个周末终于找到了解决此问题的方法,而无需编写自定义 ViewComponentResult。
在 MVC .Net Core 中,您可以将自己的 IViewLocationExpander
添加到 startup.cs 的 ConfigureServices 中的 RazorViewEngineOptions:
public void ConfigureServices(IServiceCollection services)
{
...
services.Configure<RazorViewEngineOptions>(options =>
{
options.ViewLocationExpanders.Add(new CustomLocationExpander());
});
}
这允许您添加与 ViewLocationExpanderContext
ViewName ({0})、ControllerName ({1}) 结合使用的自定义路径。
主要问题是您不能更改上下文的值,这使得更改默认视图组件的 Component/ComponentName/Default
的 ViewName 似乎是不可能的
看似不可能
这是诀窍,每次 View() 没有完全限定的视图路径时,都会调用 ExpandViewLocations。这意味着您可以添加自定义逻辑。我所做的是在 PopulateValues
方法中添加一个 catch 来检测 ViewComponents,然后添加到 context.Values
字典中,然后如果该字典具有这些自定义值,它将在 Paths 中添加路径列表使用我生成的视图名称而不是上下文。
它是完全反向兼容的,应该不会影响性能。
public class CustomLocationExpander : IViewLocationExpander
{
private const string _CustomViewPath = "CustomViewPath";
private const string _CustomController = "CustomController";
public void PopulateValues(ViewLocationExpanderContext context)
{
Regex DefaultComponentDetector = new Regex(@"^((?:[Cc]omponents))+\/+([\w\.]+)\/+(.*)");
/*
* If successful,
* Group 0 = FullMatch (ex "Components/MyComponent/Default")
* Group 1 = Components (ex "Component")
* Group 2 = Component Name (ex "MyComponent")
* Group 3 = View Name (ex "Default")
* */
var DefaultComponentMatch = DefaultComponentDetector.Match(context.ViewName);
if (DefaultComponentMatch.Success)
{
// Will render Components/ComponentName as the new view name
context.Values.Add(_CustomViewPath, string.Format("{0}/{1}", DefaultComponentMatch.Groups[1].Value, DefaultComponentMatch.Groups[2].Value));
context.Values.Add(_CustomController, context.ControllerName);
}
}
public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
{
/* Parameters:
* {2} - Area Name
* {1} - Controller Name
* {0} - View Name
*/
List<string> Paths = new List<string> {
// Default View Locations to support imported / legacy paths
"/Views/{1}/{0}.cshtml",
"/Views/Shared/{0}.cshtml",
// Adds Feature Folder Rendering
"/Features/{1}/{0}.cshtml",
"/Features/Shared/{0}.cshtml",
// Handles My Custom rendered views
"/{0}.cshtml"
};
// Add "Hard Coded" custom view paths to checks, along with the normal default view paths for backward compatability
if (context.Values.ContainsKey(_CustomViewPath))
{
// Generate full View Paths with my custom View Name and Controller Name
var CombinedPaths = new List<string>(Paths.Select(x => string.Format(x, context.Values[_CustomViewPath], context.Values[_CustomController], "")));
// Add in original paths for backward compatability
CombinedPaths.AddRange(Paths);
return CombinedPaths;
}
// Returns the normal view paths
return Paths;
}
}