C# – Partial Pages in ASP.NET Core 2.0

.net coreasp.net-coreasp.net-core-mvccrazor

TL; DR Are "partial Pages" possible in ASP.NET Core 2.0? If so, why am I getting the error described below?

I've been messing with the new Razor Page feature of ASP.NET 2.0 and having some trouble with partials. The documentation linked above refers to "partials" in several places, but only to the extent of saying mystical things like "The layouts, templates, and partials you're using with MVC controllers and conventional Razor views just work." Do they mean partial Views, like what we're used to, or some new kind of partial Page also? They show that files with the conventional Partial suffix in the name can be placed in the Pages/ folder, but the files that they mention (e.g., _ValidationScriptsPartial.cshtml) don't have the @page declaration. If "partial Pages" are indeed a new thing, then I would expect the following minimal scenario to work:

  • A Page file named Derp.cshtml:

    @page
    @namespace MyApp.Pages
    @model DerpModel
    
    <p>Member 1: @Model.Member1</p>
    <p>Member 2: @Model.Member2</p>
    
  • A class file named Derp.cshtml.cs in the same directory (nested under the first in VS 2017's Solution Explorer), containing the following class:

    namespace MyApp.Pages {  
        public class DerpModel : PageModel {
            public void OnGet() {
                Member1 = "Derp Member1";
                Member2 = "Derp Member2";
            }
    
            public string Member1 { get; set; }
            public string Member2 { get; set; }
        }
    }
    
  • A second Page file named DerpWrapper.cshtml in the same directory, with the following code:

    @page "{id}"
    @namespace MyApp.Pages
    @model DerpModel
    
    <p>Outer Member 1: @Model.Member1</p>
    <p>Outer Member 2: @Model.Member2</p>
    @Html.Partial("Derp", Model)
    
  • And of course a _ViewImports file that declares @namespace MyApp.Pages

Note that both Razor Pages have the same @model type, and the second Page basically wraps up the first one with a call to Html.Partial() and passes its Model. This should all be pretty standard stuff, but upon navigating to http://localhost:xxx/DerpWrapper, I get a 500 response due to a NullReferenceException with the following stack trace.

    MyApp.Pages.Derp_Page.get_Model()
    MyApp.Pages.Derp_Page+<ExecuteAsync>d__0.MoveNext() in Derp.cshtml
    +
    <p>Inner Member1: @Model.Member1</p>
    ...
    MyApp.Pages.DerpWrapper_Page+<ExecuteAsync>d__0.MoveNext() in DerpWrapper.cshtml
    +
    @Html.Partial("Derp", Model)
    ...

Why does the "wrapper" Page's Model equal null? Is there no way to define a Razor Page and display it from another Page or View with Html.Partial()?

Best Answer

Razor’s partial views are basically .cshtml views that are transcluded into another view, that is a razor view or a razor page. They are rendered within that view and as such inherit some of the view’s properties, e.g. its ViewData. They however do not have a controller, a page model or some other kind of “code behind” that gives you a way to run code for the partial.

As such, the primary use for partials is to extract common components that do not contain logic on their own into separate and reusable files.

The other kind of reusable components that exist in Razor are view components. View components are similar to partials but they do have an actual class that backs them and that allows you to add your own logic to the view component.

At the moment, view components follow the MVC layout for views, meaning the view component is actually a class and you return a view from its Invoke method which causes the view engine to locate that .cshtml in the Views folder. David Fowler confirmed to me though, that they are working on a Razor page like experience for view components too, so in a later version of ASP.NET Core, you will eventually be able to write a .cshtml view component and add some code-behind to it (just like Razor pages).

Coming back to your question though, at the moment there are two kinds of entry points for an MVC route: An MVC controller, possibly returning a view, and a Razor page. Neither of these can invoke one or the other inside of them though. If you are rendering a view, you can load partials or view components but not other “full” views or Razor pages. And if you’re rending a Razor page, you also can load partials or view components, but still no other “full” views or Razor pages.

So what you want to do is simply not possible because a Razor page is supposed to be an entry point to the route, and while invoking any view as a partial may work (in some situations), you will certainly neither get the controller for that view nor the page model running for it. If you want to invoke something that has complex logic, you should use view components instead.