TempData In Razor Pages

TempData is a storage container for data that needs to be available to a separate HTTP request. The canonical example for illustrating how TempData works involves providing feedback to the user after a form submission that results in the user being redirected to another page (Post-Redirect-Get).

Here is a very simple form:

<form method="post">
    <input type="text" asp-for="EmailAddress"/>
    <span asp-validation-for="EmailAddress"></span><br/>
    <button>Submit</button>
</form>

In the following code snippet, a PageModel property called FormResult has been decorated with the TempData attribute, which enables both automatic storing of values in, and loading values from the TempData dictionary.

public class IndexModel : PageModel
{
    [TempData]
    public string FormResult { get; set; }
    [BindProperty]
    public string EmailAddress { get; set; }
    // ...

The property has a value assigned to it when the form is posted.

public IActionResult OnPost()
{
    try
    {
        var email = new MailAddress(EmailAddress);
        FormResult = "You have provided a valid email address";
        return RedirectToPage("/success");
    }
    catch(FormatException)
    {
        ModelState.AddModelError("EmailAddress", "Invalid email address");
        return Page();
    }
}

If the validation test passes, the user is redirected to another page (success.cshtml). There, the TempData value is accessed using a string indexer:

<h4>@TempData["FormResult"]</h4>

Alternatively, you can add a property to the SuccessPageModel, decorated with the TempData attribute:

public class SuccessPageModel : PageModel
{
    [TempData]
    public string FormResult { get; set; }
    ...
}

Then you can more simply reference the model property in the page itself:

<h4>@Model.FormResult</h4>

Note

In ASP.NET Core 2.0 (and earlier versions if you are using MVC), the index value is prefixed with "TempDataProperty-", so you would have to use the following code to access the value:

<h4>@TempData["TempDataProperty-FormResult"]</h4>

If you want to change this behaviour in ASP.NET Core 2.0, you can use the MvcOptions to change the default behaviour:

services.AddMvc().AddViewOptions(options =>
{
    options.SuppressTempDataAttributePrefix = true; 
});

Once the value has been read, it is marked for deletion and is therefore not available for subsequent requests.

Retaining TempData Values

If you want to access a TempData value and then retain it for a further request, you have two options. The first is the Peek method, which allows you to read a value without it being marked for deletion:

var temp = TempData.Peek("FormResult");

The second option is to use the Keep method after you have accessed the value. This has the effect of marking the value for retention. The Keep method provides two options. You can either specify that the whole dictionary is retained:

var result = TempData["FormResult"];
TempData.Keep();

Or you can just mark individual values for retention:

var result = TempData["FormResult"];
TempData.Keep("FormResult")

Storage Mechanisms

The default storage mechanism for TempData is cookies, which is enabled by default in a typical Razor Pages application.

TempData cookie

Alternatively, you can use Session State.

You can configure various options related to the TempData cookie in the ConfigureServices method. The following example changes the name of the cookie:

services.Configure<CookieTempDataProviderOptions>(options =>
{
    options.Cookie.Name = "MyTempDataCookie";
});

Configure Cookie

Session State

Most browsers limit the size of cookies and therefore the data that can be stored in them. If you exceed this limit, you will get a 400 HTTP Error code: The size of the request headers is too long. If you need to store large amounts of data in TempData, you can use the session provider instead. The three steps to implement this are:

  1. Call the AddSessionStateTempDataProvider method in ConfigureServices (chained to AddMvc below)
  2. Call AddSession in ConfigureServices
  3. Call app.UseSession in the Configure method
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddSessionStateTempDataProvider();
    services.AddSession();
}

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    app.UseStaticFiles();
    app.UseSession();
    app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller}/{action=Index}/{id?}");
    });
}

Limitations

You can store simple values in TempData - strings, booleans and numeric types, but if you try to store complex types, you will encounter an InvalidOperationException:

The '[name of the property]' property with TempDataAttribute is invalid. The 'Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataSerializer' cannot serialize an object of type '[name of the property]'.

If you want to use TempData to store complex types, you must serialize it to a string-based format yourself. JSON is the recommended format to use because it is relatively compact (compared to XML) and JSON.Net is included as part of the default project template.

The following class contains two extension methods: one for serialising data to JSON and the other for deserialising it:

public static class TempDataExtensions
{
    public static void Set<T>(this ITempDataDictionary tempData, string key, T value) where T : class
    {
        tempData[key] = JsonConvert.SerializeObject(value);
    }

    public static T Get<T>(this ITempDataDictionary tempData, string key) where T : class
    {
        tempData.TryGetValue(key, out object o);
        return o == null ? null : JsonConvert.DeserializeObject<T>((string)o);
    }
}

Usage is straightforward. The second line sets a TempData entry, and the third retrieves it:

var contact = new Contact { FirstName = "Mike", Domain = "learnrazorpages.com" };
TempData.Set("Mike", contact);
var mike = TempData.Get<Contact>("Mike");

TempData And Model Binding

Model binding and TempData conflict with each other in the sense that they both provide a mechanism to populate page properties with values from an HTTP request. One must execute after the other, and the default behaviour is that TempData population takes place after model binding. There is nothing to stop you decorating a page property with both the BindProperty and TempData attributes:

[BindProperty, TempData]
public string Foo { get; set; }

However, any posted form value that is bound to the property will be overwritten by whatever is in TempData, which is usually nothing, or null. If you need to assign a model bound value to TempData, you should make the assignment manually:

[BindProperty]
public string Foo { get; set; }

public void OnPost()
{
    TempData["Foo"] = foo;
}
Last updated: 19/06/2023 08:33:34

© 2018 - 2024 - Mike Brind.
All rights reserved.
Contact me at Outlook.com