"
 
 
 
ASP.NET (snapshot 2017) Microsoft documentation and samples

Introduction to session and application state in ASP.NET Core

By Rick Anderson, Steve Smith, and Diana LaRose

HTTP is a stateless protocol. A web server treats each HTTP request as an independent request and does not retain user values from previous requests. This article discusses different ways to preserve application and session state between requests.

Session state

Session state is a feature in ASP.NET Core that you can use to save and store user data while the user browses your web app. Consisting of a dictionary or hash table on the server, session state persists data across requests from a browser. The session data is backed by a cache.

ASP.NET Core maintains session state by giving the client a cookie that contains the session ID, which is sent to the server with each request. The server uses the session ID to fetch the session data. Because the session cookie is specific to the browser, you cannot share sessions across browsers. Session cookies are deleted only when the browser session ends. If a cookie is received for an expired session, a new session that uses the same session cookie is created.

The server retains a session for a limited time after the last request. You can either set the session timeout or use the default value of 20 minutes. Session state is ideal for storing user data that is specific to a particular session but doesn’t need to be persisted permanently. Data is deleted from the backing store either when you call Session.Clear or when the session expires in the data store. The server does not know when the browser is closed or when the session cookie is deleted.

[!WARNING] Do not store sensitive data in session. The client might not close the browser and clear the session cookie (and some browsers keep session cookies alive across windows). Also, a session might not be restricted to a single user; the next user might continue with the same session.

The in-memory session provider stores session data on the local server. If you plan to run your web app on a server farm, you must use sticky sessions to tie each session to a specific server. The Windows Azure Web Sites platform defaults to sticky sessions (Application Request Routing or ARR). However, sticky sessions can affect scalability and complicate web app updates. A better option is to use the Redis or SQL Server distributed caches, which don’t require sticky sessions. For more information, see (xref:)Working with a Distributed Cache. For details on setting up service providers, see Configuring Session later in this article.

## TempData

ASP.NET Core MVC exposes the TempData property on a controller. This property stores data until it is read. The Keep and Peek methods can be used to examine the data without deletion. TempData is particularly useful for redirection, when data is needed for more than a single request. TempData is implemented by TempData providers, for example, using either cookies or session state.

### TempData providers

ASP.NET Core 2.x

In ASP.NET Core 2.0 and later, the cookie-based TempData provider is used by default to store TempData in cookies.

The cookie data is encoded with the Base64UrlTextEncoder. Because the cookie is encrypted and chunked, the single cookie size limit found in ASP.NET Core 1.x does not apply. The cookie data is not compressed because compressing encrypted data can lead to security problems such as the CRIME and BREACH attacks. For more information on the cookie-based TempData provider, see CookieTempDataProvider.

ASP.NET Core 1.x

In ASP.NET Core 1.0 and 1.1, the session state TempData provider is the default.


### Choosing a TempData provider

Choosing a TempData provider involves several considerations, such as:

  1. Does the application already use session state for other purposes? If so, using the session state TempData provider has no additional cost to the application (aside from the size of the data).
  2. Does the application use TempData only sparingly, for relatively small amounts of data (up to 500 bytes)? If so, the cookie TempData provider will add a small cost to each request that carries TempData. If not, the session state TempData provider can be beneficial to avoid round-tripping a large amount of data in each request until the TempData is consumed.
  3. Does the application run in a web farm (multiple servers)? If so, there is no additional configuration needed to use the cookie TempData provider.

[!NOTE] Most web clients (such as web browsers) enforce limits on the maximum size of each cookie, the total number of cookies, or both. Therefore, when using the cookie TempData provider, verify the app won’t exceed these limits. Consider the total size of the data, accounting for the overheads of encryption and chunking.

### Configure the TempData provider

ASP.NET Core 2.x

The cookie-based TempData provider is enabled by default. The following Startup class code configures the session-based TempData provider:

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Builder;
   2:  using Microsoft.AspNetCore.Hosting;
   3:  using Microsoft.Extensions.Configuration;
   4:  using Microsoft.Extensions.DependencyInjection;
   5:   
   6:  public class StartupTempDataSession
   7:  {
   8:      public StartupTempDataSession(IConfiguration configuration)
   9:      {
  10:          Configuration = configuration;
  11:      }
  12:   
  13:      public IConfiguration Configuration { get; }
  14:   
  15:      #region snippet_TempDataSession
  16:      public void ConfigureServices(IServiceCollection services)
  17:      {
  18:          services.AddMvc()
  19:              .AddSessionStateTempDataProvider();
  20:   
  21:          services.AddSession();
  22:      }
  23:   
  24:      public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  25:      {
  26:          app.UseSession();
  27:          app.UseMvcWithDefaultRoute();
  28:      }
  29:      #endregion
  30:  }

ASP.NET Core 1.x

The following Startup class code configures the session-based TempData provider:

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Builder;
   2:  using Microsoft.AspNetCore.Hosting;
   3:  using Microsoft.Extensions.Configuration;
   4:  using Microsoft.Extensions.DependencyInjection;
   5:  using Microsoft.Extensions.Logging;
   6:   
   7:  public class StartupTempDataSession
   8:  {
   9:      public StartupTempDataSession(IHostingEnvironment env)
  10:      {
  11:          var builder = new ConfigurationBuilder()
  12:              .SetBasePath(env.ContentRootPath)
  13:              .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
  14:              .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
  15:              .AddEnvironmentVariables();
  16:          Configuration = builder.Build();
  17:      }
  18:   
  19:      public IConfigurationRoot Configuration { get; }
  20:   
  21:      #region snippet_TempDataSession
  22:      public void ConfigureServices(IServiceCollection services)
  23:      {
  24:          services.AddMvc();
  25:          services.AddSession();
  26:      }
  27:   
  28:      public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
  29:      {
  30:          app.UseSession();
  31:          app.UseMvcWithDefaultRoute();
  32:      }
  33:      #endregion
  34:  }


Ordering is critical for middleware components. In the preceding example, an exception of type InvalidOperationException occurs when UseSession is invoked after UseMvcWithDefaultRoute. See (xref:)Middleware Ordering for more detail.

[!IMPORTANT] If targeting .NET Framework and using the session-based provider, add the Microsoft.AspNetCore.Session NuGet package to your project.

Query strings

You can pass a limited amount of data from one request to another by adding it to the new request’s query string. This is useful for capturing state in a persistent manner that allows links with embedded state to be shared through email or social networks. However, for this reason, you should never use query strings for sensitive data. In addition to being easily shared, including data in query strings can create opportunities for Cross-Site Request Forgery (CSRF) attacks, which can trick users into visiting malicious sites while authenticated. Attackers can then steal user data from your app or take malicious actions on behalf of the user. Any preserved application or session state must protect against CSRF attacks. For more information on CSRF attacks, see Preventing Cross-Site Request Forgery (XSRF/CSRF) Attacks in ASP.NET Core.

Post data and hidden fields

Data can be saved in hidden form fields and posted back on the next request. This is common in multi-page forms. However, because the client can potentially tamper with the data, the server must always revalidate it.

Cookies

Cookies provide a way to store user-specific data in web applications. Because cookies are sent with every request, their size should be kept to a minimum. Ideally, only an identifier should be stored in a cookie with the actual data stored on the server. Most browsers restrict cookies to 4096 bytes. In addition, only a limited number of cookies are available for each domain.

Because cookies are subject to tampering, they must be validated on the server. Although the durability of the cookie on a client is subject to user intervention and expiration, they are generally the most durable form of data persistence on the client.

Cookies are often used for personalization, where content is customized for a known user. Because the user is only identified and not authenticated in most cases, you can typically secure a cookie by storing the user name, account name, or a unique user ID (such as a GUID) in the cookie. You can then use the cookie to access the user personalization infrastructure of a site.

HttpContext.Items

The Items collection is a good location to store data that is needed only while processing one particular request. The collection’s contents are discarded after each request. The Items collection is best used as a way for components or middleware to communicate when they operate at different points in time during a request and have no direct way to pass parameters. For more information, see Working with HttpContext.Items, later in this article.

Cache

Caching is an efficient way to store and retrieve data. You can control the lifetime of cached items based on time and other considerations. Learn more about Caching.

## Working with Session State

Configuring Session

The Microsoft.AspNetCore.Session package provides middleware for managing session state. To enable the session middleware, Startup must contain:

The following code shows how to set up the in-memory session provider.

ASP.NET Core 2.x

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Builder;
   2:  using Microsoft.Extensions.DependencyInjection;
   3:  using System;
   4:   
   5:  public class Startup
   6:  {
   7:      public void ConfigureServices(IServiceCollection services)
   8:      {
   9:          services.AddMvc();
  10:   
  11:          // Adds a default in-memory implementation of IDistributedCache.
  12:          services.AddDistributedMemoryCache();
  13:   
  14:          services.AddSession(options =>
  15:          {
  16:              // Set a short timeout for easy testing.
  17:              options.IdleTimeout = TimeSpan.FromSeconds(10);
  18:              options.Cookie.HttpOnly = true;
  19:          });
  20:      }
  21:   
  22:      public void Configure(IApplicationBuilder app)
  23:      {
  24:          app.UseSession();
  25:          app.UseMvcWithDefaultRoute();
  26:      }
  27:  }

ASP.NET Core 1.x

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Builder;
   2:  using Microsoft.Extensions.DependencyInjection;
   3:  using System;
   4:   
   5:  public class Startup
   6:  {
   7:      public void ConfigureServices(IServiceCollection services)
   8:      {
   9:          services.AddMvc();
  10:   
  11:          // Adds a default in-memory implementation of IDistributedCache.
  12:          services.AddDistributedMemoryCache();
  13:   
  14:          services.AddSession(options =>
  15:          {
  16:              // Set a short timeout for easy testing.
  17:              options.IdleTimeout = TimeSpan.FromSeconds(10);
  18:              options.CookieHttpOnly = true;
  19:          });
  20:      }
  21:   
  22:      public void Configure(IApplicationBuilder app)
  23:      {
  24:          app.UseSession();
  25:          app.UseMvcWithDefaultRoute();
  26:      }
  27:  }


You can reference Session from HttpContext once it is installed and configured.

If you try to access Session before UseSession has been called, the exception InvalidOperationException: Session has not been configured for this application or request is thrown.

If you try to create a new Session (that is, no session cookie has been created) after you have already begun writing to the Response stream, the exception InvalidOperationException: The session cannot be established after the response has started is thrown. The exception can be found in the web server log; it will not be displayed in the browser.

Loading Session asynchronously

The default session provider in ASP.NET Core loads the session record from the underlying IDistributedCache store asynchronously only if the ISession.LoadAsync method is explicitly called before the TryGetValue, Set, or Remove methods. If LoadAsync is not called first, the underlying session record is loaded synchronously, which could potentially impact the ability of the app to scale.

To have applications enforce this pattern, wrap the DistributedSessionStore and DistributedSession implementations with versions that throw an exception if the LoadAsync method is not called before TryGetValue, Set, or Remove. Register the wrapped versions in the services container.

Implementation Details

Session uses a cookie to track and identify requests from a single browser. By default, this cookie is named “.AspNet.Session”, and it uses a path of “/”. Because the cookie default does not specify a domain, it is not made available to the client-side script on the page (because CookieHttpOnly defaults to true).

To override session defaults, use SessionOptions:

ASP.NET Core 2.x

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Builder;
   2:  using Microsoft.Extensions.DependencyInjection;
   3:  using System;
   4:   
   5:  public class StartupCopy
   6:  {
   7:      #region snippet1
   8:      public void ConfigureServices(IServiceCollection services)
   9:      {
  10:          services.AddMvc();
  11:   
  12:          // Adds a default in-memory implementation of IDistributedCache.
  13:          services.AddDistributedMemoryCache();
  14:   
  15:          services.AddSession(options =>
  16:          {
  17:              options.Cookie.Name = ".AdventureWorks.Session";
  18:              options.IdleTimeout = TimeSpan.FromSeconds(10);
  19:          });
  20:      }
  21:  #endregion
  22:   
  23:      public void Configure(IApplicationBuilder app)
  24:      {
  25:          app.UseSession();
  26:          app.UseMvcWithDefaultRoute();
  27:      }
  28:  }

ASP.NET Core 1.x

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Builder;
   2:  using Microsoft.Extensions.DependencyInjection;
   3:  using System;
   4:   
   5:  public class StartupCopy
   6:  {
   7:      #region snippet1
   8:      public void ConfigureServices(IServiceCollection services)
   9:      {
  10:          services.AddMvc();
  11:   
  12:          // Adds a default in-memory implementation of IDistributedCache.
  13:          services.AddDistributedMemoryCache();
  14:   
  15:          services.AddSession(options =>
  16:          {
  17:              options.CookieName = ".AdventureWorks.Session";
  18:              options.IdleTimeout = TimeSpan.FromSeconds(10);
  19:          });
  20:      }
  21:  #endregion
  22:   
  23:      public void Configure(IApplicationBuilder app)
  24:      {
  25:          app.UseSession();
  26:          app.UseMvcWithDefaultRoute();
  27:      }
  28:  }


The server uses the IdleTimeout property to determine how long a session can be idle before its contents are abandoned. This property is independent of the cookie expiration. Each request that passes through the Session middleware (read from or written to) resets the timeout.

Because Session is non-locking, if two requests both attempt to modify the contents of session, the last one overrides the first. Session is implemented as a coherent session, which means that all the contents are stored together. Two requests that are modifying different parts of the session (different keys) might still impact each other.

Setting and getting Session values

Session is accessed through the Session property on HttpContext. This property is an ISession implementation.

The following example shows setting and getting an int and a string:

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Mvc;
   2:  using Microsoft.AspNetCore.Http;
   3:  using System;
   4:   
   5:  namespace WebAppSession.Controllers
   6:  {
   7:      #region snippet1
   8:      public class HomeController : Controller
   9:      {
  10:          const string SessionKeyName = "_Name";
  11:          const string SessionKeyYearsMember = "_YearsMember";
  12:          const string SessionKeyDate = "_Date";
  13:   
  14:          public IActionResult Index()
  15:          {
  16:              // Requires using Microsoft.AspNetCore.Http;
  17:              HttpContext.Session.SetString(SessionKeyName, "Rick");
  18:              HttpContext.Session.SetInt32(SessionKeyYearsMember, 3);
  19:              return RedirectToAction("SessionNameYears");
  20:          }
  21:          public IActionResult SessionNameYears()
  22:          {
  23:              var name = HttpContext.Session.GetString(SessionKeyName);
  24:              var yearsMember = HttpContext.Session.GetInt32(SessionKeyYearsMember);
  25:   
  26:              return Content($"Name: \"{name}\",  Membership years: \"{yearsMember}\"");
  27:          }
  28:          #endregion
  29:   
  30:          #region snippet2
  31:          public IActionResult SetDate()
  32:          {
  33:              // Requires you add the Set extension method mentioned in the article.
  34:              HttpContext.Session.Set<DateTime>(SessionKeyDate, DateTime.Now);
  35:              return RedirectToAction("GetDate");
  36:          }
  37:   
  38:          public IActionResult GetDate()
  39:          {
  40:              // Requires you add the Get extension method mentioned in the article.
  41:              var date = HttpContext.Session.Get<DateTime>(SessionKeyDate);
  42:              var sessionTime = date.TimeOfDay.ToString();
  43:              var currentTime = DateTime.Now.TimeOfDay.ToString();
  44:   
  45:              return Content($"Current time: {currentTime} - "
  46:                           + $"session time: {sessionTime}");
  47:          }
  48:          #endregion
  49:      }
  50:  }

If you add the following extension methods, you can set and get serializable objects to Session:

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Http;
   2:  using Newtonsoft.Json;
   3:   
   4:  public static class SessionExtensions
   5:  {
   6:      public static void Set<T>(this ISession session, string key, T value)
   7:      {
   8:          session.SetString(key, JsonConvert.SerializeObject(value));
   9:      }
  10:   
  11:      public static T Get<T>(this ISession session,string key)
  12:      {
  13:          var value = session.GetString(key);
  14:          return value == null ? default(T) : 
  15:                                JsonConvert.DeserializeObject<T>(value);
  16:      }
  17:  }

The following sample shows how to set and get a serializable object:

[!code-csharpMain]

   1:  using Microsoft.AspNetCore.Mvc;
   2:  using Microsoft.AspNetCore.Http;
   3:  using System;
   4:   
   5:  namespace WebAppSession.Controllers
   6:  {
   7:      #region snippet1
   8:      public class HomeController : Controller
   9:      {
  10:          const string SessionKeyName = "_Name";
  11:          const string SessionKeyYearsMember = "_YearsMember";
  12:          const string SessionKeyDate = "_Date";
  13:   
  14:          public IActionResult Index()
  15:          {
  16:              // Requires using Microsoft.AspNetCore.Http;
  17:              HttpContext.Session.SetString(SessionKeyName, "Rick");
  18:              HttpContext.Session.SetInt32(SessionKeyYearsMember, 3);
  19:              return RedirectToAction("SessionNameYears");
  20:          }
  21:          public IActionResult SessionNameYears()
  22:          {
  23:              var name = HttpContext.Session.GetString(SessionKeyName);
  24:              var yearsMember = HttpContext.Session.GetInt32(SessionKeyYearsMember);
  25:   
  26:              return Content($"Name: \"{name}\",  Membership years: \"{yearsMember}\"");
  27:          }
  28:          #endregion
  29:   
  30:          #region snippet2
  31:          public IActionResult SetDate()
  32:          {
  33:              // Requires you add the Set extension method mentioned in the article.
  34:              HttpContext.Session.Set<DateTime>(SessionKeyDate, DateTime.Now);
  35:              return RedirectToAction("GetDate");
  36:          }
  37:   
  38:          public IActionResult GetDate()
  39:          {
  40:              // Requires you add the Get extension method mentioned in the article.
  41:              var date = HttpContext.Session.Get<DateTime>(SessionKeyDate);
  42:              var sessionTime = date.TimeOfDay.ToString();
  43:              var currentTime = DateTime.Now.TimeOfDay.ToString();
  44:   
  45:              return Content($"Current time: {currentTime} - "
  46:                           + $"session time: {sessionTime}");
  47:          }
  48:          #endregion
  49:      }
  50:  }

Working with HttpContext.Items

The HttpContext abstraction provides support for a dictionary collection of type IDictionary<object, object>, called Items. This collection is available from the start of an HttpRequest and is discarded at the end of each request. You can access it by assigning a value to a keyed entry, or by requesting the value for a particular key.

In the sample below, Middleware adds isVerified to the Items collection.

Later in the pipeline, another middleware could access it:

For middleware that will only be used by a single app, string keys are acceptable. However, middleware that will be shared between applications should use unique object keys to avoid any chance of key collisions. If you are developing middleware that must work across multiple applications, use a unique object key defined in your middleware class as shown below:

Other code can access the value stored in HttpContext.Items using the key exposed by the middleware class:

This approach also has the advantage of eliminating repetition of “magic strings” in multiple places in the code.

Application state data

Use (xref:)Dependency Injection to make data available to all users:

  1. Define a service containing the data (for example, a class named MyAppData).
  1. Add the service class to ConfigureServices (for example services.AddSingleton<MyAppData>();).
  2. Consume the data service class in each controller:

Common errors when working with session

A typical example:

Someone stores a shopping basket in session. The user adds an item but the commit fails. The app doesn’t know about the failure so it reports the message “The item has been added”, which isn’t true.

The recommended way to check for such errors is to call await feature.Session.CommitAsync(); from app code when you’re done writing to the session. Then you can do what you like with the error. It works the same way when calling LoadAsync.

Additional Resources





Comments ( )
<00>  <01>  <02>  <03>  <04>  <05>  <06>  <07>  <08>  <09>  <10>  <11>  <12>  <13>  <14>  <15>  <16>  <17>  <18>  <19>  <20>  <21>  <22>  <23
Link to this page: //www.vb-net.com/AspNet-DocAndSamples-2017/aspnetcore/fundamentals/app-state.htm
<SITEMAP>  <MVC>  <ASP>  <NET>  <DATA>  <KIOSK>  <FLEX>  <SQL>  <NOTES>  <LINUX>  <MONO>  <FREEWARE>  <DOCS>  <ENG>  <CHAT ME>  <ABOUT ME>  < THANKS ME>