locked
[MVC] Strange NullReferenceException RRS feed

  • Question

  • User-1370514677 posted

    Hi everyone,

    I'm facing a strange NullReferenceException.

    Indeed I've built a simple Publish Article form.

    An article is :

    using System.ComponentModel.DataAnnotations;
    
    namespace DevAstuces.Models
    {
        public class Article
        {
            [Key]
            public int Id { get; set; }
            public virtual User Author { get; set; }
            [Required]
            public virtual Category Category { get; set; }
            [Required]
            public string Title { get; set; }
            [Required]
            public string Description { get; set; }
            [Required]
            public string Content { get; set; }
    
            public enum ArticleStatus
            {
                Pending,
                Rejected,
                Validated
            }
    
            public ArticleStatus Status { get; set; }
        }
    }

    Category.cs

    using System.ComponentModel.DataAnnotations;
    
    namespace DevAstuces.Models
    {
        public class Category
        {
            [Key]
            public int Id { get; set; }
            public byte[] Image { get; set; }
            
            [Required]
            public string Name { get; set; }
        }
    }

    Publish.cshtml

    <form id="editor_pane" method="POST">
            <input type="list" list="Category" placeholder="Catégorie" name="Category">
            <datalist id="Category">
            @foreach(var category in (List<Category>)ViewData["CategoryList"])
            {
                <option value="@category.Name"></option>
            }
            </datalist>
    
            <input type="text" placeholder="Saisissez le titre de votre astuce/article ici" name="Title">
            
            <input type="text" placeholder="Description" name="Description">
    
            <div id="editor_controls">
                <button type="button" onclick="previewArticle()">Prévisualiser</button>
                <input value="Valider" type="submit">
            </div>
    </form>

    ArticleController.cs

    using System.Linq;
    using System.Threading.Tasks;
    using DevAstuces.Data;
    using DevAstuces.Models;
    using Microsoft.AspNetCore.Mvc;
    
    namespace DevAstuces.Controllers
    {
        public class ArticleController : Controller
        {
            private readonly ArticleContext _articlecontext;
    
            public ArticleController(ArticleContext articlecontext)
            {
                _articlecontext = articlecontext;
            }
    
            [HttpGet]
            public IActionResult Publish()
            {
                ViewData["CategoryList"] = _articlecontext.Category.ToList();
    
                return View();
            }
    
            [HttpPost]
            public IActionResult Publish(Article article)
            {
                if(!ModelState.IsValid)
                    return View();
    
                return RedirectToAction("Index","Home");
            }
        }
    }

    So, for the moment being, I get a NullReferenceException when submitting the form as a HttpPost request (no extra operations actually) which should just RedirectToAction as there is no logic inside the Publish method.

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

    But I do have two categories present in my DB and the generated HTML code is just here :

    <form id="editor_pane" method="POST">
            <input type="list" list="Category" placeholder="Catégorie" name="Category">
            <datalist id="Category">
                <option value="NodeJS"></option>
                <option value="csharp"></option>
            </datalist>
    
            <input type="text" placeholder="Saisissez le titre de votre astuce/article ici" name="Title">
            
            <input type="text" placeholder="Description" name="Description">
    </form>

    Thanks in advance for your help

    Tuesday, February 16, 2021 10:43 AM

Answers

All replies

  • User475983607 posted

    The code you've shared has a lot of potential errors. 

    The null exception always shows the line and item that's null.  It is a lot easier if you share what is null rather than making us guess. 

    Tuesday, February 16, 2021 12:22 PM
  • User-1370514677 posted

    Hi @mgebhard,

    Here is the full Error Output :

            @foreach(var category in (List<Category>)ViewData["CategoryList"])
            {
                <option value="@category.Name"></option>
            }
    System.NullReferenceException: Object reference not set to an instance of an object.
       at AspNetCore.Views_Article_Publish.<ExecuteAsync>b__16_4() in C:\Users\Théo\Code\DevAstuces\Views\Article\Publish.cshtml:line 17
       at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.GetChildContentAsync(Boolean useCachedResult, HtmlEncoder encoder)
       at Microsoft.AspNetCore.Mvc.TagHelpers.RenderAtEndOfFormTagHelper.ProcessAsync(TagHelperContext context, TagHelperOutput output)
       at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperRunner.<RunAsync>g__Awaited|0_0(Task task, TagHelperExecutionContext executionContext, Int32 i, Int32 count)
       at AspNetCore.Views_Article_Publish.<ExecuteAsync>b__16_1()
       at Microsoft.AspNetCore.Mvc.Razor.RazorPage.RenderSectionAsyncCore(String sectionName, Boolean required)
       at Microsoft.AspNetCore.Mvc.Razor.RazorPage.RenderSection(String name, Boolean required)
       at AspNetCore.Views_Shared__Layout.<ExecuteAsync>b__13_1() in C:\Users\Théo\Code\DevAstuces\Views\Shared\_Layout.cshtml:line 16
       at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()
       at AspNetCore.Views_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, Nullable`1 statusCode)
       at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
       at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, String contentType, Nullable`1 statusCode)
       at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result)
       at Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context)
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResultFilterAsync>g__Awaited|29_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 where exception was thrown ---
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_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 where exception was thrown ---
       at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
       at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
       at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
       at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
       at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

    Is it what you meant ? :(

    Tuesday, February 16, 2021 3:01 PM
  • User-474980206 posted

    the error means

    ViewData["CategoryList"]

    is null. so your action is failing to set it to a valid value.

    Tuesday, February 16, 2021 5:01 PM
  • User-1370514677 posted

    Hi @bruce,

    Thank you for your answer.

    Yes, that's what I understand but as you can see the HTML code is well generated and the data from the DB is perfectly fine :

            [HttpGet]
            public IActionResult Publish()
            {
                ViewData["CategoryList"] = _articlecontext.Category.ToList();
    
                return View();
            }
            <input type="list" list="Category" placeholder="Catégorie" name="Category">
            <datalist id="Category">
            @foreach(var category in (List<Category>)ViewData["CategoryList"])
            {
                <option value="@category.Name"></option>
            }
            </datalist>
            <input type="list" list="Category" placeholder="Catégorie" name="Category">
            <datalist id="Category">
                <option value="NodeJS"></option>
                <option value="csharp"></option>
            </datalist>

    Tuesday, February 16, 2021 5:05 PM
  • User475983607 posted

    valenciano8

    but as you can see the HTML code is well generated and the data from the DB is perfectly fine

    It sounds like you are telling us the code you shared does not cause the null exception.  Can you share the code that does cause the null exception.  Typically, the line of code is displayed in the developer error page in red.    

    Perhaps you can debug the code?  

    https://docs.microsoft.com/en-us/visualstudio/debugger/debugger-feature-tour?view=vs-2019

    Tuesday, February 16, 2021 9:47 PM
  • User-1370514677 posted

    It's this line :

    @foreach(var category in (List<Category>)ViewData["CategoryList"])

    But I don't understand how it can produce a NullReferenceException as the HTML Code hereafter shows that it retreived entities through CategoryList :

    <form id="editor_pane" method="POST">
            <input type="list" list="Category" placeholder="Catégorie" name="Category">
            <datalist id="Category">
                <option value="NodeJS"></option>
                <option value="csharp"></option>
            </datalist>
    
            <input type="text" placeholder="Saisissez le titre de votre astuce/article ici" name="Title">
            
            <input type="text" placeholder="Description" name="Description">
    </form>

    Tuesday, February 16, 2021 9:52 PM
  • User475983607 posted

    I suspect there is a model validation error because you are missing one of the [Required] fields in the HTML Form.  There's also the Category type which will always be null.  Anyway, the exception happens because the options do not exist in the model posted to Publish Action and you are not passing the mode or option to the View.

    Simply use the Visual Studio debugger to view what your code is doing.  Most likely you made a few assumptions like the option persist between requests but you did not verify your assumption is correct.

    Tuesday, February 16, 2021 9:58 PM
  • User-1982062310 posted

    Re your problem -- set breakpoints in each method of your controller to see which method is invoked on POST. It's possible that your code is not set up correctly and it tries to render "Publish" page on post response, and whatever controller's method is doing that rendering is not supplying the ViewData.

    Also,

    valenciano8

    ViewData["CategoryList"] = _articlecontext.Category.ToList();

    Time and again I urge developers to avoid using ViewData and similar ways of data transfer and use View Models instead.

    Just try passing your list as a model to the view instead:

    valenciano8

    [HttpGet]
            public IActionResult Publish()
            {
                return View(_articlecontext.Category.ToList());
            }

    ...and in your Publish.cshtml declare the model type at the top:

    @model List<Category>
    ...
    @foreach(var category in Model)

    You will avoid most of the run time errors this way and will have compile time type-checking out of the box.

    Tuesday, February 16, 2021 10:27 PM
  • User-1370514677 posted

    Hi,

    I found the issue, it is due to the ModelState validation and the fact that I pass an Article object to the view : 

            [HttpPost]
            public IActionResult Publish(Article article)
            {
                if(!ModelState.IsValid)
                    return View(article); // Error occurs here
    
                return RedirectToAction("Index","Home");
            }

    Now that I use a "View Model" approach, I have a more detailed error output :

    InvalidOperationException: The model item passed into the ViewDataDictionary is of type 'DevAstuces.Models.Article', but this ViewDataDictionary instance requires a model item of type 'System.Collections.Generic.List`1[DevAstuces.Models.Category]'.

    So, according to this error I should also define a @model directive into the CSHTML file but I can only give one ?

    @model List<DevAstuces.Models.Category>
    @model DevAstuces.Models.Article // The "model" directive may only occur once per document
    @{
        Layout = "_Layout";
        ViewData["Title"] = "Publier";
    }
    
    @section page_styles{
        <link href="~/css/Article/publish.css" type="text/css" rel="stylesheet">
    }
    
    @section page_content{
        <h1>Publier</h1>
    
        <form id="editor_pane" method="POST">
            <input type="list" list="Category" placeholder="Catégorie" name="Category">
            <datalist id="Category">
            @foreach(var category in Model)
            {
                <option value="@category.Name"></option>
            }
            </datalist>
    
            <input type="text" placeholder="Saisissez le titre de votre astuce/article ici" name="Title">
            
            <input type="text" placeholder="Description" name="Description">
    
            <div id="editor_controls">
                <button type="button" onclick="previewArticle()">Prévisualiser</button>
                <input value="Valider" type="submit">
            </div>
        </form>
    }
    
    @section page_scripts{
        <script src="~/js/Article/editor.js"></script>
    }

    How can I proceed ?

    Thursday, February 18, 2021 2:01 PM
  • User475983607 posted

    So, according to this error I should also define a @model directive into the CSHTML file but I can only give one ?

    A view model (C# class) can contain one or many models.  This is generally referred to a complex type or in MVC a View Model when the data is used to generate the UI (View).

    I recommend going through the Getting Started tutorials on this site as they cover these basic MVC programming concepts.

    https://docs.microsoft.com/en-us/aspnet/core/tutorials/first-mvc-app/start-mvc?view=aspnetcore-5.0&tabs=visual-studio

    https://docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/?view=aspnetcore-5.0

    • Marked as answer by Anonymous Thursday, October 7, 2021 12:00 AM
    Thursday, February 18, 2021 2:20 PM
  • User-1982062310 posted

    As the error message says, you can only have one model directive. If your View needs to make use of both a single Category (selected for POST) as well as the list of all categories, then you need to create a separate model class that has those fields, and pass an instance of that class.

    Also, in the following code you need to pass an instance of the model as well:

                return RedirectToAction("Index","Home", model); // add "model" to list of params

    Alternatively you can look at post-and-redirect pattern, where a different controller endpoint & a different view is used to display a successful post result.

    Thursday, February 18, 2021 8:32 PM
  • User-1370514677 posted

    Hi,

    Thanks a lot, the ViewModel approach solved the issue and is indeed advised for compile time type checking.

    Here is my ViewModel composed of a List of Category and an Article :

    using System.Collections.Generic;
    using DevAstuces.Models;
    
    namespace DevAstuces.ViewModels
    {
        public class ArticleViewModel
        {
            public List<Category> Category { get; set; }
            public Article Article { get; set; }
        }
    }

    Here is my new ArticleController code for the Publish Method :

            [HttpGet]
            public IActionResult Publish()
            {
                var ArticleViewModel = new ArticleViewModel();
                ArticleViewModel.Category = _articlecontext.Category.ToList();
                
                return View(ArticleViewModel);
            }
    
            [HttpPost]
            public IActionResult Publish(ArticleViewModel ArticleViewModel)
            {
                ArticleViewModel.Category = _articlecontext.Category.ToList();
    
                if(!ModelState.IsValid)
                    return View(ArticleViewModel);
    
                return RedirectToAction("Index","Home");
            }

    NB : the documentation should have a "more beginner friendly" tutorial for MVMC and also should explain how to create a ViewModel with multiple models because I had to search on the web for that..

    Friday, February 19, 2021 8:09 AM