Monday, September 8, 2014

Microsoft 70-486: Configure state management

Exam Objectives


Choose a state management mechanism (in-process and out of process state management), plan for scalability, use cookies or local storage to maintain state, apply configuration settings in web.config file, implement sessionless state (for example, QueryString)

Quick Overview of Training Materials


MSDN - ASP.NET State Management Overview and Recommendations
MSDN - HttpRequest.QueryString Property
State Management and Scalability
Two Ways of Passing HTML5 Web Storage Data to ASP.NET
avoid hitting DB to authenticate a user on EVERY request in stateless web app architecture?


Choose state management mechanism


What mechanism you choose to manage state in your application is going to depend on several factors, including the amount of time state data needs to be maintained (short time, full session, between sessions), what resources are available (bandwidth, memory), the scope of the state (single user or application wide), and the network environment in which the application will operate (single server vs server farm):
  • Cache - The Cache class is an in-proc method of saving information for a relatively short period. The simplest way to store information in the Cache is a simple assignment. Cache["key"] = value.  However, by using the "Add" method on the Cache object, it is possible to include dependency and expiration information to the information being cached.  The "Insert" method has overloads that also allow for similar functionality. The difference between the two methods lie in how they treat existing data.  When using "Add" to add data to a key that already exists, the existing data is kept, whereas "Insert" will replace the existing data.

    While the Cache object is accessible across users, it is in-process and thus only accessible on a single web server.  This limits it's usefulness in web farm scenarios.  One potential use case for the cache object is when retrieving the data is expensive (perhaps a call to an external web service) and it doesn't necessarily have to be real time.  Performance gains can be realized when the same piece of data can be accessed from the cache by multiple users rather than retrieving the data fresh for every request.
     
  • Application State - Application state, like the cache object, is accessible across users.  Also like the cache object, application state is local to a single web server.  Data is written to the Application state in much the same way as the cache: Application.["key"] = value;  Value in this case can be any object.

    Unlike the cache object, memory usage by the application state is not managed by the .NET framework, which can result in performance degradation if the application state is overused.  Since application state data is typically desired for the life of the application, it is a common recommendation to use the Application_Start event to assign data to the application state.  Another way to leverage Application State is to create objects in the Static Objects collection via Global.asax.  This is done using the <object runat="server" scope="Application"> </object> tag.

    Normally, Application State data is lost when the application is shut down.  However, it is possible to use the Application_End event to run code that will save changes to application state to a database or serialize into an XML file.

    Another important consideration when using Application state is that of concurrency.  If there is potential for multiple users to be making changes to the Application state simultaneously, it is important to use the Application.Lock() and Application.Unlock() methods to make sure changes are thread safe.
     
  • Session - Storing state information in the session makes sense when information is user specific and said information only needs to be maintained while the user is interacting with the site (that is, it doesn't need to be persisted between visits.) Session variables are set much like the cache and application values: Session["key"] = value;  When retrieving session values, they must be explicitly cast to the correct type.  Session values can be removed using Session.Remove("key") or Session.RemoveAll.  Session.SessionId returns the unique identifier for that user's session, and Session.Timeout will set the amount of time that a session can remain inactive before it expires. Timeout can also be set in the web.config file: <sessionState timeout="5" /> would set the timeout to 5 minutes.

    I discussed In-proc vs Out-proc with regards to sessions in my blog on planning for distributed applications.  In a nutshell, by default SessionState is saved InProc, that is to say in the AppDomain the user first encountered.  If following requests are sent to another web server in a farm (because affinity is not set), then the session data from the first server will not be available.  In these cases it is necessary to use a state server or sql server to make session data available across the farm. One source of overhead for using an out proc session provider is the fact that it is necessary to serialize all the objects in the session for storage, a step that is not necessary for in-proc sessions [link].

    There are performance considerations that can improve the responsiveness of the site.  Because session data can be turned off on a per-page basis, partially or fully disabling it for a given page may help with load times as requests will not be subject to session lock.
     
  • Cookies - Cookies are small files that are written to the users machine by the browser to store key-value pairs in a persistent manner.  Unlike Session data, which disappears when the user logs off, cookies are accessible across sessions.  Because cookies are transmitted between the client and server, there are several important considerations to take into account when using them.  Cookies are size limited to about 4Kb, so they are not suitable for large amounts of data.  Also, because cookies are attached to every request and response, having large amounts of cookie data can have an impact on performance.

    Cookies are created on the Response object. This can be done by accessing the Response.Cookies collection with an indexer: Response.Cookies["key"].Value = value, Response.Cookies["key"].Expires = DateTime.  Alternatively, a new HttpCookie object can be added to the collection using the Add() method: Response.Cookies.Add(myCookie);  Cookies sent to the server from the browser from the client are read off the Request object in much the same way.  It is important to know which object to use for your purpose. It is perfectly legit to assign values to the request cookie collection, though this is probably not what was intended (I learned this lesson the hard way...).  Also, for cookies to persist, they must have a set expiration date.  A cookie without an expiration date is a temporary cookie and will disappear when the browser is closed.

    One last consideration on cookies.  They are typically transmitted as plain text, which mean that sensitive information like passwords and personal identifiable information shouldn't be stored in them.  While it is possible to encrypt this data, storing it securely on the server side is probably a better option.
     
  • QueryString - The query string is data that has been appended to the URL of the page.  Because query strings are visible to the user, they are not a good candidate for transmitting sensitive data.  Also, some browsers impose limits on the query string.  The query string is an appropriate state management mechanism for passing simple data between pages.  Reading and writing to the query string is done in much the same way as other collections.  The Request and Response objects have the QueryString collections attached to them.  Writing a new query string variable looks like so: Response.QueryString["key"] = value.  As with cookies, be sure to use the Response object to send a variable to the browser and the Request object to read variables sent to the server from the user.
     
  • HttpContext.Items - While session data will last for the life of the user's session, it may be desirable to have state information that only persists for the life of a single request.  This property of the HttpContext object is a weakly typed IDictionary, so data read from it will need to be cast before it can be used.  Items are assigned to and read from the dictionary using the indexer: HttpContext.Items["key"] = value.
     
  • Profile - The ASP.NET Profile API is a built in feature that allows you to create a user profile and save that information per user in a persistent manner. The profile provider included with .NET saves data to a local SQL database in the App_Data folder, but it is possible to create a custom profile provider. The use cases for Profile are similar to Session, with the most notable exception being the ability to save data across sessions.

    The Profile API is configured in the web.config file.  Defining a profile provider and connection string will allow multiple instances of the application to use the same database, which avoids the issues inherent in inproc session state.  The profile itself is also defined in the web.config file using the <profile> tag and <properties> subtag.  Attributes on these tags define whether anonymous profiles are enabled, default values, serialization format, and data types.  Properties can also be grouped using the <group> tag.  Using serializeAs = "Binary" and type = "{your type name}", it is possible to make properties use custom classes, provided the class is marked with [Serializable].

    Because properties are defined, reading and writing profile values are not done via an indexer as with other state management mechanisms, but instead are accessed as object properties.  So an address property on the profile would be assigned as so: Profile.Address = "123 Main St.".  If the complete address is part of a property group called "Address", the street number might be assigned as so: Profile.Address.Street = "123 Main St".  
ViewState is used in Web Forms but not in MVC.

Plan for scalability


Certain state management options are better than others with regard to scalability.
  • Application state is in-proc only, so it is not suited to applications that will be running in a web farm or multi threaded environment.
  • Session state will scale when configured correctly.  Using a Session Server or a Sql provider will both allow session information to be shared across all the servers in a web farm.  Sessions can be turned off on a per page basis, which may improve performance
  • Profile API uses a database by default, which should allow for some degree of built in scalability.
  • Cookies, QueryString, and Items collections all are tied to the HttpRequest, and the scalability of these state management mechanisms is limited more by their inbuilt restrictions (Cookie and QueryString length) and bandwidth considerations.

The ASP.NET website lays out some web development best practices, including recommendations to improve scalability.   One recommendation is to keep the web servers stateless.  This means that state information should not be stored in memory on the web server.  This is possible using an out-proc state server (they recommend a distributed cache provider like AppFabric or Azure Caching Service), or request based state (URL, QueryString, Cookies, Items).

Other facets of scalability were discussed in my post on designing a distributed app.

Use cookies or local storage


Cookies are essentially small text files that accompany the HttpRequests a browser makes to a website.  Cookies are associated with a given website, so the same cookies will be passed among all the pages on your site.  Essentially cookies tie a given user to a website and persist the information written into them between visits.  This can be very useful for storing data about a given user, such as perferences, or a userid that can be used to retrieve other information from the server side.  Expiration dates can be configured on cookies.  Cookies are a simple, resource efficient way to persist state information.

Because cookies are stored as plain text, it is inadvisable to store sensitive information such as PPI or passwords in them.  While cookies are only meant to be accessible to the domain that created them, there is potential for malicious actors to find ways of accessing them.  Also, size limitations constrain the amount of information in a cookie to only 4 kilobytes.  Also, since cookies are included with EVERY request to the domain, they can potentially add significant bandwidth overhead.  Finally, cookies are not always available on the client.  While modern browsers all accept cookies, users are generally able to configure the browser to refuse them, so they are not necessarily a state maintenance mechanism that is reliably available.

An alternative to cookies is the Web Storage API that is a part of HTML 5.  I discussed it some in a previous post for the 70-480 test.  Because the storage API lives entirely on the client, using it requires the use of client side code (JavaScript or JQuery, for example).  Local storage comes in two variaties that are analogous to permanent and temporary cookies: localStorage is persistant across sessions, whereas sessionStorage only lasts until the session ends.  Both use identical syntax to set and retrieve values.

Using local storage, setting a key-value pair looks like this:

localStorage.setItem("key", "value");

And retrieving the above item is just a matter of asking for the value associated with the key:

value = localStorage.getItem("key");

The key can also be retrieved with the key() method, along with the index of the key

key = localStorage.key(index);

This is how the demonstration below loops through all the keys in local storage to display the contents.  As with any HTML 5 feature, it is important to do feature detection before attempting to rely on it.  The Dive Into Html5 series includes a simple feature check for local storage:

function supports_html5_storage() {
  try {
    return 'localStorage' in window && window['localStorage'] !== null;
  } catch (e) {
    return false;
  }
}

Or it points out that, alternatively, you can use Moderizer to check if localStorage is supported.

The fiddle below demonstrates the use of localStorage using JavaScript:


Apply settings in web.config


Session and Profile state management can be configured through the web.config file.  The other state management mechanisms (Cache object, Application state, QueryString, Context.Items, Cookies) do not require configuration.

<sessionState>


The <sessionState> element is used to configure session behavior, and is located under the <system.web> element. Much of the following information is taken directly from the MSDN page for the <sessionState> element:

Attributes:

  • mode:  Specifies where to store state information.  Default is InProc
    • InProc - state is stored in memory in a worker process on the local app server
    • StateServer - OutProc, uses a state service that may be running on a seperate server
    • SQLServer - OutProc, state information stored in a SQL Server database
    • Custom - Uses a custom datastore, which is specified with a <provider> child element
    • Off - session state is disabled.
  • timeout:  Sets the amount of time (in minutes) that a session can be idle before it expires.  Max value is one year (525601) for InProc and StateServer modes.
  • compressionEnabled: when true, session data is compressed. Default is false.
  • cookieName: Specifies the name of the cookie that stores the session id. Default it "ASP.NET_SessionId"
  • cookieless:  Specifies how cookies are used. Default value is "UseCookies"
    • AutoDetect - ASP.NET determines if the browser supports cookies.  If cookies are supported, cookies are used, otherwise the query string is used.
    • UseCookies - Cookies are used regardless of browser support
    • UseDeviceProfile - ASP.NET determines if cookies can be used based on HttpBrowserCapabilities setting. If cookies are supported they are used, otherwise the query string is used.
    • UseUri - The query string is used to store session identifier regardless of cookie support.
  • regenerateExpiredSessionId:  specifies whether a new session id will be generated when an expired id is passed by the client.  By default session ids are only reissued in cookieless mode when this attribute is true. Default value is true.
  • sessionIDManagerType: Fully qualified type of the session id manager.
  • useHostingIdentity:  Specified how the application authenticates with the session state store.When true, the application connects using the credentials of the hosting process or the application impersonation identity (if it's configured).  When false, the application connects with the credentials for the current request.
  • partitionResolverType:  Specifies where to store session data, used to partition session state data across multiple nodes in OutProc mode.  Overrides sqlConnectionString and stateConnectionString attributes.
  • Only used when mode is set to SQLServer:
    • sqlConnectionString: specifies a connection string for connecting to SQL Server
    • sqlCommandTimeout: number of seconds a SQL command can be idle before timing out. Default is 30 seconds.
    • sqlConnectionRetryInterval:  the time interval in seconds between connection attempts.  Default is 0 seconds.
    • allowCustomSqlDatabase:  boolean value specifying whether a custom database can be specified instead of the ASP.NET default.  When false, the sqlConnectionString cannot contain an initial catalog or database.  Default is false.
  • Only used when mode is set to StateServer:
    • stateConnectionString: specifies the server name or address and port of the session server.  Port value must be 42424.
    • stateNetworkTimeout:  the amount of time, in seconds, that the TCP/IP connection between the application and the state server can be idle before the connection times out.
  • Only used when mode is set to Custom:
    • customProvider:  the name of the session state provider (specified in the <provider> child element) to use as the data store.
The only child element of <sessionState> is the <providers> element, which is used to specify a provider when using Custom mode.  The element actually contains a collection of providers.  Each provider must inherit from the SessionStateStoreProviderBase class.


<profile>


The <profile> element is used to configure the ASP.NET Profile API.

Attributes:
  • enabled: Boolean value turns profiles on and off. Default is true.
  • defaultProvider: The name of the default profile provider. Default value is AspNetSqlProfileProvider.
  • inherits: references a type derived from ProfileBase.  ASP.NET generates a class called ProfileCommon from the specified type and uses it in the Profile property of the HttpContext.
  • automaticSaveEnabled:  When the ProfileModule object detects that a change to the profile has been made, is changes the value of the IsDirty property to true.  If autosave is enabled, modified profile data is saved at the end of page execution.
The profile element has two child elements. The <properties> element is required and defines groups of properties on the profile (e.g. Address, First Name, Phone Number, etc.).  The <providers> element is an optional element that can be used to define a profile provider.  This would be used if you weren't using the default AspNetSqlProfileProvider.

Implement sessionless state


Performance and scalability requirements may necessitate the use of sessionless state.  When developing for a webfarm scenario, maintaining stateless web servers facilitates easy horizontal scaling.  Although outproc session state services using a state server or sql server enable sharing of state data, these create additional overhead as well as presenting a single point of failure of session data.  Also, the use of sessions can create more tightly coupled, less readable and debuggable code.  Avoiding session state all together is considered a best practice for MVC applications. Even in applications that utilize sessions, using sessionless controller whenever possible will improve performance; because sessions are not thread safe, multiple calls to the same controller method from the same session (e.g. multiple AJAX calls) will cause the request to queue and be served sequentially, leading to possible degradation in performance. In a Windows Azure environment, keeping the web server stateless has the added benefit of easy scale out (including automated and scheduled scaling)

A common use of session state is to store authentication information, however in ASP.NET the Identity framework does not require sessions to operate.  Authentication information is stored in the cookie:





The Exam Ref discusses the need for sessionless state mechanisms to maintain a unique identifier.  The image above is one example where ASP.NET stores tokens in the cookie, but there are other options:
  • Hidden form field
  • Keep in client side storage (localStorage or sessionStorage)
  • Add identifier to query string
  • Use routing to parse from route and add unique id to URL
All of these methods have their pros and cons.  Hidden form fields must be included on every page and will only flow back to the server is that page is submitted.  QueryString requires that you are requesting the same page or following a link.  Also, the use of QueryString or routing in URL can create the potential for security vulnerabilities.  Web Storage API is not automatically available to the server, so if the server needs information from that store then cliet side code will have to retrieve and transmit that information somehow, be it on a header, hidden form field, query string, etc.

Control over session state can be configured down to the controller level.  A controller can be made sessionless by using annotation:

[SessionState(System.Web.SessionState.SessionStateBehavior.Disabled)]
    public class HomeController : Controller
    {
       ....
    }

If a method on this controller returned a JsonResult to an Ajax call, multiple calls from the session would not be forced to execute sequentially, but could instead be served in parallel, improving performance.  The [SessionState] attribute is only valid at the class level, so individual methods can not be made sessionless (trying will throw a compile error... I know, I tried haha).

No comments:

Post a Comment