Search code examples
razorasp.net-core-mvcasp.net-core-viewcomponent

ASP.NET View Components: Remove default path to view


I want to return a View with a specific path to the view like so, return View(~/Views/Home). The type to be returned is an IViewComponentResult.

However, when the site gets rendered, it tries to find the view under: Components/{controller-name}/~/Views/Home.

So I want to ask you guys, if you know a smart way to delete Components/{controller-name}/ from the path. My view component class is deriving from the ViewComponent class.

The problem regarding this is that right now, you have to have a "Components" folder with the name of the controller as a subfolder which contains a Default.cshtml file. I do not like to have all my components inside one folder. I hope you have a solution.


Solution

  • The convention happened in ViewViewComponentResult hence if you don't want the convention, you will have to implement your own IViewComponentResult

    since mvc is open source, you could copy all the ViewViewComponentResult and then change the convention bit.

    so essentially two things has to be done:

    1. create your own IViewComponentResult - lets call it MyViewViewComponentResult
    2. create a helper in your implemented ViewComponent to replace the original View() - purpose is to returns the custom MyViewViewComponentResult you created at step 1

    create your own IViewComponentResult

    the convention happened at ExecuteAsync:

    public class ViewViewComponentResult : IViewComponentResult
    {
        // {0} is the component name, {1} is the view name.
        private const string ViewPathFormat = "Components/{0}/{1}";
        private const string DefaultViewName = "Default";
    
        ....
    
        public async Task ExecuteAsync(ViewComponentContext context)
        {
            ....
            if (result == null || !result.Success)
            {
                // 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;
                var qualifiedViewName = string.Format(
                    CultureInfo.InvariantCulture,
                    ViewPathFormat,
                    context.ViewComponentDescriptor.ShortName,
                    viewName);
    
                result = viewEngine.FindView(viewContext, qualifiedViewName, isMainPage: false);
            }
    
            ....
        }
    
        .....
    }
    

    so change it to your need

    create a helper in your implemented ViewComponent which replace the original View

    so base on original

    /// <summary>
    /// Returns a result which will render the partial view with name <paramref name="viewName"/>.
    /// </summary>
    /// <param name="viewName">The name of the partial view to render.</param>
    /// <param name="model">The model object for the view.</param>
    /// <returns>A <see cref="ViewViewComponentResult"/>.</returns>
    public ViewViewComponentResult View<TModel>(string viewName, TModel model)
    {
        var viewData = new ViewDataDictionary<TModel>(ViewData, model);
        return new ViewViewComponentResult
        {
            ViewEngine = ViewEngine,
            ViewName = viewName,
            ViewData = viewData
        };
    }
    

    you would do something like

    /// <summary>
    /// Returns a result which will render the partial view with name <paramref name="viewName"/>.
    /// </summary>
    /// <param name="viewName">The name of the partial view to render.</param>
    /// <param name="model">The model object for the view.</param>
    /// <returns>A <see cref="ViewViewComponentResult"/>.</returns>
    public MyViewViewComponentResult MyView<TModel>(string viewName, TModel model)
    {
        var viewData = new ViewDataDictionary<TModel>(ViewData, model);
        return new MyViewViewComponentResult
        {
            ViewEngine = ViewEngine,
            ViewName = viewName,
            ViewData = viewData
        };
    }
    

    so in future you would call MyView() instead of View()

    more info check out another SO: Change component view location in Asp.Net 5