0

Is there a way to check if a ViewComponent's View actually exists before trying to render it?

I know there are FindView and GetView methods for searching Views from e.g. Controller, I'm looking for something similar for ViewComponents.

Inside a ViewComponent, there's a ViewEngine, but when I try using that like ...

ViewEngine.FindView(ViewContext, "SomeView", false);

... the SearchedLocations for the result is not looking for ViewComponents:

[0]: "/Views/Home/SomeView.en-US.cshtml"
[1]: "/Views/Home/SomeView.en.cshtml"
[2]: "/Views/Home/SomeView.cshtml"
[3]: "/Views/Shared/SomeView.en-US.cshtml"
[4]: "/Views/Shared/SomeView.en.cshtml"
[5]: "/Views/Shared/SomeView.cshtml"
[6]: "/Pages/Shared/SomeView.en-US.cshtml"
[7]: "/Pages/Shared/SomeView.en.cshtml"
[8]: "/Pages/Shared/SomeView.cshtml"

Note: Asking this question because the try-catch block does not catch the exception that occurs with a missing view, for example ...

public IViewComponentResult Invoke()
{      
    try
    {
        return View();
    }
    catch (Exception e)
    {
        return Content(string.Empty);
    }
}

... would cause an unhandled exception due to missing view file.

Dely
  • 83
  • 3
  • 9

2 Answers2

0

As you can see here the ViewComponentResult.ViewComponent method builds the path of the desired view using the hard-coded values that are declared at the top of the class. So internally, the call to ViewEngine.FindView is performed passing as the view name the relative path to the desired view, which is Components/{ComponentName}/{ViewName}. The view engine then searches this path using the ones you saw in your example as the bases.

To answer your question, you should edit your code to do the same, so it should read

ViewEngine.FindView(ViewContext, "Components/ViewComponentName/SomeView", false);

Example for a view which is placed into /Views/Shared/Components/Navigation/Default.cshtml.

step
  • 1
  • 1
  • 2
  • What you said is true, however ... I've been using an above code with View("SomeView") and for it to check the path Components/ViewComponentName/ path since ViewComponents check the default path as well. The thing that is strange for me is that the ViewEngine.FindView does not check that path at all. – Dely Jul 14 '21 at 11:18
  • Besides, since I've extended the view engine's search path for Views (storing some of the View files outside the actual project) and it had no problem rendering those views, I'm looking for a similar solution with FindView. If there's no other way that defining the path like you showed above, I guess I'll have to hard-code it per ViewComponent. – Dely Jul 14 '21 at 11:21
  • Have you tried using the `services.Configure(...` to configure the search path behaviour of the engine? – step Jul 14 '21 at 12:59
  • I did, and it is working perfectly. Like I said, I have no problems rendering Views for the ViewComponent with View("SomeView"), as I've extended the search paths for the search engine. The only issue for me is that the ViewEngine.FindView seems to ignore that configuration and I have to use the "Components/ViewComponentName/" in the path because of it. It's not a big issue, but instead of it being dynamic, I have to wrap it with a hard-coded string instead. – Dely Jul 15 '21 at 12:51
  • Sorry, I didn't understand you already did that. Anyway, I think that given the situation, hardcoding at least the `Components/` part of the path is inevitable since AspNetCore does it itself. But to avoid writing the name of the component every time I think you could create a `BaseViewComponent` class which inherits from `ViewComponent` and subclass it when you are creating a new ViewComponent. There you could create a convenience method which automatically builds the path to the `Components/{Component Name}/` folder and invokes the `ViewEngine.FindView` with the built path. – step Jul 16 '21 at 06:46
0

You can inject IViewComponentSelector into your controller to check if a component exists:

Controller:

private readonly IViewComponentSelector _selector;
        public HomeController(IViewComponentSelector selector)
        {
            _selector = selector;
        }
public IActionResult Index()
        {
            if (_selector.SelectComponent("CheckBox1") != null)
            {
                return Ok("exist");
            }
            else 
            {
                return Ok("Not Found");
            }
        }

result(ViewComponent name is CheckBox): enter image description here

Yiyi You
  • 16,875
  • 1
  • 10
  • 22