在Asp.Net 5中更改组件视图位置

时间:2015-11-04 17:05:54

标签: asp.net-core asp.net-core-mvc

在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。

对我而言,它更有意义......是否可能?

2 个答案:

答案 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;
    }
}