Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ASP.NET Core MVC View Component search path

In the documentation here: https://learn.microsoft.com/en-us/aspnet/core/mvc/views/view-components?view=aspnetcore-2.2

The runtime searches for the view in the following paths:

/Views/{Controller Name}/Components/{View Component Name}/{View Name}
/Views/Shared/Components/{View Component Name}/{View Name}
/Pages/Shared/Components/{View Component Name}/{View Name}

How can I add another path here?

I'd like to have my view components with their respective controllers in one project folder named components like this.

/Components/{View Component Name}/{View Name}

My motivation:

I find out my view components have their own JS and CSS files. I have all JS bundled and minimized in one site.min.js and all CSS bundled and minimized in their site.min.css. The JS are always something like $(function() { ... }) and the CSS are always written in a way that order does not matter so bundling all without knowing the order is not a problem.

Some of those view components have javascripts which change their state on server e.g. AJAX call to a controller's action which returns some JSON or the whole view component's HTML.

Since Controllers are just a C# classes they can be in any folder but it feels stupid to move the controller with the relevant AJAX action to the "Views" folder.

In the end I'd like to have a "component" (not really a "view component" only) like this:

/Components/SomeViewComponent/Default.cshtml
/Components/SomeViewComponent/SomeViewComponentController.cs
/Components/SomeViewComponent/SomeViewComponent.cs
/Components/SomeViewComponent/SomeViewComponent.css
/Components/SomeViewComponent/SomeViewComponent.js
/Components/SomeViewComponent/SomeViewComponent.en.resx
/Components/SomeViewComponent/SomeViewComponent.cs-CZ.resx
like image 937
Mirek Avatar asked Apr 17 '19 06:04

Mirek


People also ask

What is the default path of view for view component?

The search path applies to projects using controllers + views and Razor Pages. The default view name for a view component is Default, which means your view file will typically be named Default. cshtml . You can specify a different view name when creating the view component result or when calling the View method.

How do I invoke a view component?

The view component supports two methods to call a view component is Invoke and InvokeAsync . + Invoke Asynchronously: Example of a View Component that uses the InvokeAsync method. + Invoke Synchronously: Example of a View Component that uses the Invoke method. Or Invoke() method with parameters.

What is a ViewComponent in asp net core?

What is a ViewComponent? ViewComponent was introduced in ASP.NET Core MVC. It can do everything that a partial view can and can do even more. ViewComponents are completely self-contained objects that consistently render html from a razor view.

Which is a default view search path for the Razor view engine?

By default Razor Pages are stored in Pages folder under project root.


2 Answers

So after an hour digging into the aspnetcore repository, I found the component's search path is hardcoded and then combined with normal view search paths.

// {0} is the component name, {1} is the view name.
private const string ViewPathFormat = "Components/{0}/{1}";

This path is then sent into the view engine

result = viewEngine.FindView(viewContext, qualifiedViewName, isMainPage: false);

The view engine then produces the full path, using the configurable view paths.

  • Views/Shared/Components/Cart/Default.cshtml
  • Views/Home/Components/Cart/Default.cshtml
  • Areas/Blog/Views/Shared/Components/Cart/Default.cshtml

If you want to place your view components into a root folder named "Components" as I wanted, you can do something like this.

services.Configure<RazorViewEngineOptions>(o =>
{
    // {2} is area, {1} is controller,{0} is the action
    // the component's path "Components/{ViewComponentName}/{ViewComponentViewName}" is in the action {0}
    o.ViewLocationFormats.Add("/{0}" + RazorViewEngine.ViewExtension);        
});

That's kind of ugly in my opinion. But it works.

You can also write your own expander like this.

namespace TestMvc
{
    using Microsoft.AspNetCore.Mvc.Razor;
    using System.Collections.Generic;

    public class ComponentViewLocationExpander : IViewLocationExpander
    {
        public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
        {
            // this also feels ugly
            // I could not find another way to detect
            // whether the view name is related to a component
            // but it's somewhat better than adding the path globally
            if (context.ViewName.StartsWith("Components"))
                return new string[] { "/{0}" + RazorViewEngine.ViewExtension };

            return viewLocations;
        }

        public void PopulateValues(ViewLocationExpanderContext context) {}
    }
}

And in Startup.cs

services.Configure<RazorViewEngineOptions>(o =>
{
    o.ViewLocationExpanders.Add(new ComponentViewLocationExpander());   
});
like image 149
Mirek Avatar answered Oct 24 '22 00:10

Mirek


You can add additional view location formats to RazorViewEngineOptions. As an example, to add a path that satisfies your requirement, you can use something like this:

services
    .AddMvc()
    .AddRazorOptions(o =>
    {
        // /Components/{View Component Name}/{View Name}.cshtml
        o.ViewLocationFormats.Add("/{0}.cshtml");
        o.PageViewLocationFormats.Add("/{0}.cshtml");
    });

As can be seen above, there are different properties for views (when using controllers and actions) and page views (when using Razor Pages). There's also a property for areas, but I've left that out in this example to keep it marginally shorter.

The downside to this approach is that the view location formats do not apply only to view components. For example, when looking for the Index view inside of Home, Razor will now also look for Index.cshtml sitting at the root of the project. This might be fine because it's the last searched location and I expect you're not going to have any views sitting at the root of your project, but it's certainly worth being aware of.

like image 3
Kirk Larkin Avatar answered Oct 24 '22 00:10

Kirk Larkin