Search code examples
c#asp.net-corerazor-pages

Passing model from Index to _Layout to _Header in ASP.NET Core causes NullReferenceException on get_Model()


I have an ASP.NET Core 8.0 project using Razor pages. When I visit http://localhost:5###/ and the ViewData["_Header"] is used to pass data from the Index page to the _Layout page to the _Header page, the _Header page throws a NullReferenceException on get_Model().

Index.cshtml:

@page
@{
    Layout = "_Layout";
    ViewData["_Header"] = new DemoForStackOverflow.Pages.Shared.Header() {IsArriving = true};
}
<h2>some content</h2>

_Layout.cshtml:

<!DOCTYPE html>
<html>
    <head>
        <title>Demo for Stack Overflow</title>
    </head>
    <body>
        <div id="header">
            @await Html.PartialAsync("_Header", ViewData["_Header"])
        </div>
        <h1>Appears on every page below header and above body</h1>
        <div id="body">
            @RenderBody()
        </div>
    </body>
</html>

_Header.cshtml:

@page
@model DemoForStackOverflow.Pages.Shared.Header

@if (Model.IsArriving)
{
    <p>Hello</p>
}
else
{
    <p>Goodbye</p>
}

_Header.csthml.cs:

using Microsoft.AspNetCore.Mvc.RazorPages;

namespace DemoForStackOverflow.Pages.Shared;

public class Header : PageModel
{
    public bool IsArriving { get; set; }
}

Here is the full exception message and stack trace:

Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]

An unhandled exception has occurred while executing the request.

System.NullReferenceException: Object reference not set to an instance of an object.

at AspNetCoreGeneratedDocument.Pages_Shared__Header.get_Model()
at AspNetCoreGeneratedDocument.Pages_Shared__Header.ExecuteAsync() in /home/RF/DemoForStackOverflow/Pages/Shared/_Header.cshtml:line 4
at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context) at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.RenderPartialCoreAsync(String partialViewName, Object model, ViewDataDictionary viewData, TextWriter writer) at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.PartialAsync(String partialViewName, Object model, ViewDataDictionary viewData) at AspNetCoreGeneratedDocument.Pages_Shared__Layout.b__8_1() in /home/RF/DemoForStackOverflow/Pages/Shared/_Layout.cshtml:line 8 at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync() at AspNetCoreGeneratedDocument.Pages_Shared__Layout.ExecuteAsync() at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context) at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts) at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderLayoutAsync(ViewContext context, ViewBufferTextWriter bodyWriter) at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context) at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable1 statusCode) at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable1 statusCode) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|30_0[TFilter,TFilterAsync](ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResultExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeResultFilters() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|25_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope) at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context) at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

Why is the Header model null? Is there a better way to accomplish passing data from Index to _Header using _Layout?

I have tried added debugging statements. It appears that ViewData["_Header"] is working, but somewhere past Html.PartialAsync("_Header", ViewData["_Header"]) the model value becomes null.


Solution

  • Why is the Header model null? Is there a better way to accomplish passing data from Index to _Header using _Layout?

    In ASP.NET Core Razor Pages, Html.PartialAsync cannot directly pass a page model to a partial view because the page model is tied to the Razor Page itself.

    So, to solve the issue, we need to convert the _Header page to a partial view and remove the @page directive.

    According to your requirement, I create a sample using the following code, it seems that everything works well.

    Create a HeaderViewModel class in the Data folder:

    namespace WebApplication1.Data
    {
        public class HeaderViewModel
        {
            public bool IsArriving { get; set; }
        }
    }
    

    Create a _Header.cshtml page as partial view:

    @model WebApplication1.Data.HeaderViewModel
    
    @if (Model.IsArriving)
    {
        <p>Hello</p>
    }
    else
    {
        <p>Goodbye</p>
    }
    

    In the Layout page, render the partial view:

        <div id="header"> 
            @await Html.PartialAsync("_Header", ViewData["_Header"])
        </div>
    

    Then in the Index.cshtml page, create HeaderViewModel model and use ViewData to transfer data.

    @page
    @model IndexModel
    @{
        ViewData["Title"] = "Home page";
        ViewData["_Header"] = new WebApplication1.Data.HeaderViewModel() { IsArriving = false }; 
    }
    

    After running the application, the output as below:

    enter image description here