When an action signals that a view should be rendered, the following occurs (in a simplified way):
The action returns a ViewResult object because ViewResult implements IActionResult, and its ExecuteResultAsync method is called asynchronously.
The default implementation attempts to find ViewResultExecutor from the dependency injection (DI) framework.
The FindView method is called on ViewResultExecutor, which uses an injected ICompositeViewEngine, also obtained from the DI framework, to obtain IView from the list of registered view engines.
The view engine chosen will be an implementation of IRazorViewEngine (which, in turn, extends IViewEngine).
The IView implementation uses the registered IFileProviders to load the view file.
ViewResultExecutor is then asked to invoke the view, through its ExecuteAsync method, which ends up invoking the ExecuteAsync methods of the base ViewExecutor.
ViewExecutor builds and initializes some infrastructure objects such as ViewContext and ends up invoking IView RenderAsync method.
Another service (ICompilationService) is used to compile the C# code.
The registered IRazorPageFactoryProvider creates a factory method for creating a .NET class that inherits from IRazorPage.
IRazorPageActivator is passed an instance of the new IRazorPage.
The ExecuteAsync method of IRazorPage is called.
Here, I didn't mention the filters, but they are here as well, except action filters, as I said.
Why is this important? Well, you may need to implement your own version of—say—IRazorPageActivator so that you can perform some custom initialization or DI in the Razor view, as illustrated in the following code block:
public class CustomRazorPageActivator : IRazorPageActivator { private readonly IRazorPageActivator _activator;