0

I have a school database, where there are entities such as Year, Grade, Major, ... I'm trying to create a list of years in the _Layout.cshtml and when the user chooses a year from the list, all views must retrieve data based on the selected year. So I need to first create the list and then pass the selected year to all views. I tried using a viewModel but it didn't work. Now I'm trying to make use of TempData in a way to keep the list of years and the selected year, but I keep receiving the following error.

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

Here is my AdminController:

public async Task<IActionResult> Index()
{
    var allYears = await _context.Years.ToListAsync();
    List<SelectListItem> yearList = new List<SelectListItem>();
    foreach (var item in allYears)
    {
        yearList.Add(new SelectListItem { Text = item.Name, Value = item.Id.ToString() });
    }
    TempData["YearList"] = yearList;
    TempData["CurrentYear"] = allYears.FirstOrDefault().Id.ToString();
    return View(TempData);
}

This is the list in the partial view in _Layout.cshtml. This is where I get the error. TempData is null.:

<select class="form-control" asp-for="@TempData["CurrentYear"]">
    @foreach (var item in (List<SelectListItem>)TempData["YearList"])
    {
        <option>@item.Text</option> 
    }    
</select>

I have also added this to _viewStart.cshtml.

TempData["CurrentYear"] = 1;
TempData["YearList"] = null;

I have also tried this, but the issue still remains. Is there any way I can achieve this using TempData? Or Is there a better way?

Edit1: I removed the initialization in viewStart.cshtml. But still I get the null exception error when navigating to another view.

Edit2: I used Session instead of TempData. Also I moved the dropdownlist to a separate view because I had a problem with adding a list to the session. But then RahulSharma sent a tutorial in the comment section which is very helpful. Thank you everyone.

Dahlia
  • 27
  • 6
  • could you clarify the purpose of setting values to tempdata in the `viewstart`? – Adalyat Nazirov Jun 19 '23 at 13:21
  • @AdalyatNazirov to make them global and accessible in all views – Dahlia Jun 19 '23 at 13:40
  • TempData is already global, you don't need to "initialise" it in viewstart explicitly. Moreover, in your case you override the value came from controller – Adalyat Nazirov Jun 19 '23 at 13:50
  • @AdalyatNazirov thanks! I removed it from `viewStart`. But still I have the null value problem when I navigate to another view. – Dahlia Jun 19 '23 at 13:55
  • cool! Can you now edit the question and put more info about your navigation? The code currently in question perfectly works (just tested locally). So there must bug in different places or you use complex navigation and TempData items are eliminated. – Adalyat Nazirov Jun 19 '23 at 14:14
  • TempData is [only kept until it's read by another request](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-7.0#tempdata). If you want to keep data for longer, eg an entire user's session you should use [Session State](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-7.0#session-state). Neither is suitable for lookup data that can be reused by all users. That's what the [Cache](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-7.0#cache) is for – Panagiotis Kanavos Jun 19 '23 at 14:19
  • @AdalyatNazirov I simply click on a button which navigates to an empty view! – Dahlia Jun 19 '23 at 14:29
  • what do you mean by navigating? I don't see any click handlers or forms on your DropDownList with years. If you need to "just" navigate, you could for example pass current year as query string param – Adalyat Nazirov Jun 19 '23 at 14:33
  • @AdalyatNazirov I meant clicking on a random button in the view which redirects to another view. I didn't include that part in the question. I don't know much about query string. I'll look into it. thanks. – Dahlia Jun 19 '23 at 14:38
  • ah.. yeah. after user selection, you need to "pick up" the option value and pass it to the next view. It's possible to do via form or simple redirect. – Adalyat Nazirov Jun 19 '23 at 14:41
  • @AdalyatNazirov What do you mean by "pick up"? Isn't TempData globally accessible in all views? – Dahlia Jun 19 '23 at 16:39
  • I've provided a simple example for you to try. But I would recommend you learn Asp.Net MVC using video tutorials. It won't take too much time, but you learn basis of web development – Adalyat Nazirov Jun 19 '23 at 17:57
  • @AdalyatNazirov thank you. Actually, I have watched many tutorials! And I'm still learning. – Dahlia Jun 19 '23 at 18:09
  • @Dahlia Use `Session` for your case: `Session["YearList"] = yearList` and `Session["CurrentYear"] = allYears.FirstOrDefault().Id.ToString();` . Ideally you should use a `Model` binding in your scenario – Rahul Sharma Jun 19 '23 at 19:50
  • @RahulSharma I tried using `Session`, but it doesn't work for a list. – Dahlia Jun 20 '23 at 07:36
  • @Dahlia What do you mean it does not work for a list? Can you show your attempt using `Session`? – Rahul Sharma Jun 20 '23 at 08:59
  • @RahulSharma I used `_contextAccessor.HttpContext.Session.SetString("yearList", yearList);` to assign the list to the session, but it gives me the following error: `Cannot convert from System.Collections.Generic.List to String` – Dahlia Jun 20 '23 at 09:10
  • @Dahlia This tutorial in particular should help you in solving your issue: https://www.c-sharpcorner.com/article/bind-asp-model-to-selectlist-in-layout-page-using-sessions-in-asp-net-core/ – Rahul Sharma Jun 20 '23 at 09:12

1 Answers1

2

Here is the prototype for your scenario

Controller:

[HttpGet]
public async Task<IActionResult> Index()
{
    TempData["YearList"] = new List<SelectListItem> { new ("2021", "2021"), new ("2022", "2022"), new ("2023", "2023") };
    return View();
}

[HttpPost]
public async Task<IActionResult> Index(int year)
{
    TempData["CurrentYear"] = year;
    return RedirectToAction(nameof(Index));
}

View:

<form method="post">
    <select name="year">
        @foreach (var year in (List<SelectListItem>)TempData["YearList"])
        {
            <option value="@year.Value">@year.Text</option>
        }
    </select>
    <input type="submit" value="Send"/>
</form>

<h3>Selected year: @TempData["CurrentYear"]</h3>

Explanation:

  1. [HttpGet]Index action method renders years, no selected year on this step
  2. View retrieves the values from TempData and display select control
  3. User choose the year, and click button (submit form)
  4. [HttpPost]Index accept year argument (name matches name attribute of select control). Put the selection into TempData
  5. Redirect to [HttpGet]Index
  6. Renders both list and selected value. The key point here is that if you just refresh the page it won't recover selected value again. Extraction from TempData is one-time operation (read answer https://stackoverflow.com/a/32571656/2091519)

Points to consider:

  1. pass the selected year via redirect & query string parameters rather than submitting the form (https://stackoverflow.com/a/10175571/2091519)
  2. Use Ajax (async) approach to fetch additional data after user selection (https://stackoverflow.com/a/29122117/2091519)
  3. Use ViewBag/ModelViews or any other container for passing data from contoller to view
  4. Use Cookies/Session to persist data between page reloads etc
Adalyat Nazirov
  • 1,611
  • 2
  • 14
  • 28
  • Thanks for the example. – Dahlia Jun 19 '23 at 18:12
  • I have a problem with keeping the list. It is null when navigating to another view. I mean everytime I click on another's view button, the dropdown list needs to be repopulated. I tried session to keep the list but it doesn't store a list. – Dahlia Jun 19 '23 at 18:21
  • I have tried ViewModels. It doesn't work for me. The list is in a partial view and when I add a VM to the partial view, it causes a conflict with the main view's model. – Dahlia Jun 19 '23 at 18:30
  • Session or any kind of long-live cache should be okay if you need to persist data between requests for a long time. ViewModel, TempData, and ViewBag are ways to transmit data from one layer to another - it's not about persisting data. – Adalyat Nazirov Jun 19 '23 at 19:21
  • So, for example, you can store data in session, and *before* render copy them from Session to TemData/ViewBag/etc and then reference it in your view. – Adalyat Nazirov Jun 19 '23 at 19:24
  • If you don't have more questions, please mark answer as accepted (by clicking tick next to answer) – Adalyat Nazirov Jun 20 '23 at 05:09
  • I changed the navigation and used a session. One last question: How can I pass both the selected `Value` and `Text` from the view to the controller? – Dahlia Jun 20 '23 at 07:56
  • Actually, there is nothing different. In the approach above (form) you can add for example additional inputs (including hidden) within the form, and add additional arguments (or use model) in your HttpPost method. This is example for login form https://stackoverflow.com/a/60767508/2091519 – Adalyat Nazirov Jun 20 '23 at 08:18
  • On the other hand, in your case Value is enough, as you can find Text in the controller. Usually we try to minimize form data (as we also need to validate the income) and avoid duplications. – Adalyat Nazirov Jun 20 '23 at 08:20