Let's talk about Session today. Well, I think every Asp.net developer knows about it. Especially when I was a beginner in Asp.net, I must have used it because it is really easy to store session data. Different from the previous two blogs, this time I am not going to elaborate on its usage, but I am going to talk about its shortcomings. I will also give a practical example, let's see if it has any negative impact. Of course, criticism is meaningless, and things have to be solved without sessions. Therefore, this article also provides a solution that we think can replace the Session.
The ins and outs of a Session
When we create a new website, the Session is opened in the website template code generated by VS20XX.Yes. If you do not close it, the Session is always working.You only need to use a line of code on the Page to determine whether your website is using Session,
Session ["key1"] = DateTime. Now;
It is very easy to write a Session. If the code can run without exceptions, it means that your website supports Session. We can disable web. config globally,
<SessionState mode = "Off"> </sessionState>
Run the above Code to see the yellow pages.In other words, when you access the Session, the following exception indicates that your website (or the current page) does not support the Session.
Note: If the above yellow pages appear when you access a Session on a page, the Session may be closed at the page level. In the Page command line of each aspx Page, you only need to set the EnableSessionState. This attribute has three Optional options. I have created three pages and accept the default names given by IDE.
// Default. aspx <br/> <% @ Page Language = "C #" AutoEventWireup = "true" CodeFile = "Default. aspx. cs "EnableSessionState =" True "Inherits =" _ Default "%> </p> <p> // Default2.aspx <br/> <% @ Page Language =" C # "AutoEventWireup = "true" CodeFile = "Default2.aspx. cs "EnableSessionState =" ReadOnly "Inherits =" Default2 "%> </p> <p> // Default3.aspx <br/> <% @ Page Language =" C # "AutoEventWireup = "true" CodeFile = "Default3.aspx. cs "EnableSessionState =" False "Inherits =" Default3 "%>
For Default. aspx, The EnableSessionState setting does not need to be explicitly specified because it is the Default value.The default value of this parameter on the page can also be set in web. config, for example, <pages enableSessionState = "ReadOnly">
The preceding three settings set three different Session usage methods. Next let's take a look at how this setting works for the Session.
If your web. config has the following settings:
<Compilation debug = "true">
Then, you can go to x: \ WINDOWS \ Microsoft. NET \ Framework \ v2.0.50727 \ Temporary ASP. NET Files \ websiteName \ xxxxxx \ xxxxxxxx find the [pre-compilation version] of the three aspx pages ]:
Note: The temporary directory for Asp.net compilation can also be specified in web. config, for example, <compilation debug = "true" tempDirectory = "D: \ Temp">
// Default. aspx <br/> public partial class _ Default: System. web. sessionState. IRequiresSessionState {</p> <p> // Default2.aspx <br/> public partial class Default2: System. web. sessionState. IRequiresSessionState, System. web. sessionState. IReadOnlySessionState {</p> <p> // Default3.aspx <br/> public partial class Default3 {
Alternatively, you can compile the entire website, view the definition of these classes from the generated assembly, and view the above results.
That is to say, when the settings in the Page instruction are converted into some interface [Mark] by the compiler, you may be a little curious. Why are these interfaces used? Let's take a look at this problem. Of course, we can only decompile the. net framework code to find clues. Finally, we found that in the PostMapRequestHandler event of the Application
Internal class MapHandlerExecutionStep: HttpApplication. IExecutionStep <br/>{< br/> void HttpApplication. IExecutionStep. execute () <br/>{< br/> HttpContext context = this. _ application. context; <br/> HttpRequest request = context. request; </p> <p> //.................... note the following call <br/> context. handler = this. _ application. mapHttpHandler (<br/> context, request. requestType, request. filePathObject, request. physicalPathInternal, false); <br/> //.................... <br/>}< br/>
Next, find the Handler attribute of HttpContext.
Public IHttpHandler Handler <br/>{< br/> set <br/>{< br/> this. _ handler = value; <br/> //........................... <br/> if (this. _ handler! = Null) {<br/> if (this. _ handler is IRequiresSessionState) {<br/> this. requiresSessionState = true; <br/>}< br/> if (this. _ handler is IReadOnlySessionState) {<br/> this. readOnlySessionState = true; <br/>}< br/> //........................... <br/>}< br/>
At this point, we should make it clear that the two interfaces are only a tag. Let's take a look at their definitions:
Public interface IRequiresSessionState <br/>{< br/>}< br/> public interface IReadOnlySessionState: IRequiresSessionState <br/>{< br/>}< br/>
It is completely an empty interface, just to differentiate the method of using sessions. You may want to know where the RequiresSessionState and ReadOnlySessionState attributes of HttpContext are used. The answer is in SessionStateModule. SessionStateModule is the HttpModule that implements the Session. It checks all requests and uses different processing methods based on the two attributes of HttpContext. The method is roughly as follows:
Bool requiresSessionState = this. _ rqContext. RequiresSessionState; <br/> // some judgments about requiresSessionState will be found later. </p> <p> if (! RequiresSessionState) {<br/> //....................... <br/>}</p> <p> this. _ rqReadonly = this. _ rqContext. readOnlySessionState; <br/> // some of the following operations are available for this. _ rqReadonly judgment </p> <p> if (this. _ rqReadonly) {<br/> this. _ rqItem = this. _ store. getItem (this. _ rqContext, this. _ rqId, out flag2, out span, <br/> out this. _ rqLockId, out this. _ rqActionFlags); <br/>}< br/> else {<br/> this. _ rqItem = this. _ store. getItemExclusive (this. _ rqContext, this. _ rqId, out flag2, out span, <br/> out this. _ rqLockId, out this. _ rqActionFlags); <br/> //.......................... <br/>}< br/>
This piece of code is scattered. To give an authoritative explanation of the two parameters, I will directly reference the original text in MSDN.
The session status is managed by the SessionStateModule class, which calls the session Status storage provider to read and write session data in the data storage area at different times during the request process. When the request starts, the SessionStateModule instanceGetItemExclusiveMethod or GetItem method (if the EnableSessionState page property has been set to ReadOnly) to retrieve data from the data source. If the session status value is modified at the end of the request, SessionStateModule
The instance calls SessionStateStoreProviderBase. SetAndReleaseItemExclusive to write the updated value to the session state storage area.
As mentioned above, locking affects concurrency. Let's take a look at the description of concurrency in MSDN.
Access to the ASP. NET session status belongs to each session, which means that if two different users send requests at the same time, the access to each individual session will be granted at the same time. However, if the two concurrent requests are for the same session (by using the same SessionID value), the first request will obtain exclusive access to the session information. The second request will be executed only after the first request is completed. (If the exclusive lock on session information is released because the first request exceeds the lock timeout, the second session can also obtain access .) If you set the EnableSessionState value in the @ Page command
ReadOnly, requests for read-only session information will not cause exclusive lock on session data. However, read-only requests to session data may still need to wait until the locks set by the read/write requests to the session data are removed.
ASP. NET applications are multi-threaded, so they support responses to multiple concurrent requests. Multiple concurrent requests may attempt to access the same session information. Assume that multiple frameworks in the framework set reference all ASP. NET web pages in the same application. In the framework set, independent requests of each framework can be concurrently executed on different threads of the Web server. If the ASP. NET page of each framework accesses the session state variable, multiple threads may concurrently access the session storage area. To avoid data conflicts and unexpected session state behaviors in the session storage area, SessionStateModule and SessionStateStoreProviderBase
Class provides a function,The session storage item of a specific session can be locked exclusively during ASP. NET page execution.Note that if the EnableSessionState attribute is marked as ReadOnly, the session storage item is not locked. However, other ASP. NET pages in the same application may be written to the session store. Therefore, requests for read-only session data in the store may still have to wait until the locked data is released.
In the call to the GetItemExclusive method, the session storage data is locked when the request starts. After the request is complete, the lock is released when the SetAndReleaseItemExclusive method is called.
If the SessionStateModule instance encounters a locked session data when calling the GetItemExclusive or GetItem method, the instance re-Requests the session data every half second, until the lock is released or the specified time in the ExecutionTimeout attribute has passed. If the request times out, SessionStateModule calls the ReleaseItemExclusive method to release the session storage data, and then immediately requests the session to store data.
Before the SetAndReleaseItemExclusive method is called for the current response, the locked session storage data may have been released by calling the ReleaseItemExclusive method on a separate thread. This may cause the SessionStateModule instance to set and release session state storage data that has been released and modified by other sessions. To avoid this situation, SessionStateModule provides a lock identifier for each request to modify the locked session storage data. Session storage data can be modified only when the lock identifier in the data storage area matches the lock identifier provided by SessionStateModule.
In the face of authoritative texts, it seems superfluous to explain it again. However, through the above Code Analysis and MSDN explanation, we can understand three points:
1. It explains why the PostMapRequestHandler event is earlier than the AcquireRequestState event in a series of Application events. Because SessionStateModule needs to access HttpContext. requiresSessionState, but this attribute will be sent to HttpContext again. handler can be obtained only after being assigned a value, while HttpContext. the value assignment operation of Handler is completed in the PostMapRequestHandler event. It is interesting.
2. If you do not close the Session, SessionStateModule will always be working, especially when the default settings are adopted, a series of calls will be executed for each request.
3.When Session is used, especially when default settings are used, concurrent access is affected.
Impact of Session on concurrent access
If you think the previous text may not be too easy to understand, it doesn't matter. I have made several experiment pages specially. Please continue to read it.
The first page, the main HTML part:
<Div> <br/> <B> This is Default1.aspx </B> <br/> </div> <br/>
On the first page, the background code is as follows:
Protected void Page_Load (object sender, EventArgs e) <br/>{< br/> // deliberately stops for 5 seconds. <Br/> System. Threading. Thread. Sleep (5000); <br/>}< br/>
The second page, the main HTML part (no background code ):
<Div> <br/> <B> This is Default2.aspx </B> <br/> </div> <br/>
The third page, the main HTML part (no background code ):
<Div> <br/> <B> This is Default3.aspx </B> <br/> </div> <br/>
Now it's the turn of the main framework page, the main HTML part
<Iframe src = "Default1.aspx" width = "150px"> </iframe> <br/> <iframe src = "Default2.aspx" width = "150px"> </iframe> <br /> <iframe src = "Default3.aspx" width = "150px"> </iframe> </p> <p> <br/> <asp: literal ID = "labResult" runat = "server"> </asp: Literal> <br/> </p> <br/>
The main framework page, the background code section:
Public partial class _ Default: System. web. UI. page <br/> {<br/> private static int count = 0; </p> <p> protected void Page_Load (object sender, EventArgs e) <br/> {<br/> // because no Session is used on the previous page, you can simply use it here. <Br/> Session ["Key1"] = System. threading. interlocked. increment (ref count); <br/>}</p> <p> protected override void OnPreRender (EventArgs e) <br/>{< br/> base. onPreRender (e); </p> <p> this. labResult. text = Session ["Key1"]. toString (); <br/>}< br/>
The above code is too simple and I will not talk about it much. Now let's take a look at the page display. The first thing we can see is this:
After 5 seconds, all the sub-framework pages will be loaded.
The sample code above is clearly written. Only defa1.1.aspx can be executed for 5 seconds. The subsequent two pages will be displayed without any delay. However, the results show that:The first page request blocked all subsequent page requests !!
In fact, the same scenario will happen on websites with intensive Ajax,In such websites, a page may also send multiple requests, and the next request is sent before [the previous request has not been completed, at this time, the request process is actually the same as the above sub-framework. Some people may want to ask: My website does not close the Session, and Ajax is also used a lot. Why is it not like this? In fact, as mentioned above, the concurrency impact here is limited to the multiple requests of the same user. In addition, if the server responds quickly, we usually cannot notice it,However, it actually blocks subsequent requests.
We do not feel the congestion of the Session because the blocking time is not long enough,My test cases make this phenomenon more obvious.Believe it or not, I believe it.
For the concurrency issue, I would like to talk about my idea: Microsoft uses the lock design in the Session, although it will affect the concurrency, but the design itself is safe and thorough. Because there may be conflicts between modification and read operations in multiple requests of one user. Microsoft is a platform and they have to consider this issue. But in reality, the possibility of such conflicts should be very small, or we can control it. In this case, this problem is unacceptable.
Summary of the shortcomings of the Session
Everything has two sides, both advantages and disadvantages. When evaluating a thing, we should analyze its advantages and disadvantages comprehensively; otherwise, the evaluation will lose its meaning. Today, before criticizing the shortcomings of the Session, let's take a look at its advantages:Only one line of code is required to conveniently maintain user session data.This is actually a great implementation!
But why is it still not used? For example, I will not use it unless I make a small demonstration. Why?
I personally think that this great implementation is still somewhat restrictive, or has some shortcomings. Now let's take a look at the shortcomings of the Session:
1. When mode = "InProc", that is, the default setting, data is easy to lose. Why? The website will be restarted for various reasons.
2. When mode = "InProc", the more things saved by the Session, the more memory occupied by the server. For websites with a large number of online users, the memory pressure on the server will be relatively high.
3. When mode = "InProc", the scalability of the program will be affected because the memory of the server cannot be shared among multiple servers.
4. although the Session supports scalability, that is, setting mode = "SQLServer" or mode = "StateServer", this method still has the following Disadvantages: during each request, no matter whether you use session data or not, it is a waste of resources.
5. If you do not close the Session, SessionStateModule will always be working. Especially when you use the default settings, a series of calls will be executed for each request. A waste of resources.
6. Concurrency issues, which have been explained earlier, are also examples.
7. When you use a non-Cookie Session, the Session will be used by default for security purposes.Regenerate expired session identifiersIn this case, if you use the http post method to initiate a request with an expired session ID, all the data sent will be lost. This is because ASP. NET performs redirection to ensure that the browser has a new session identifier in the URL.
It is undeniable that some people may think these shortcomings are acceptable. They are more interested in the simple and easy-to-use advantages of the Session, so the Session is still perfect.
Do not use the Session Substitution Method
If you think that some of the shortcomings of the Session I listed earlier are unacceptable, you can refer to the alternative solution I proposed.
1. If you need to maintain some simple data during the call process before and after a page, you can use the <input type = "hidden"/> element to save the data.
2. You want to share session data on the entire website, just like mode = "InProc. In this case, we can use cookies and Cache to control the storage and loading of session data. The specific method is also simple: configure a Key (ignore if any) for the request, and then use this Key to access the Cache to complete the storage and loading logic. If you want to use more than one session data, you can customize a type or use a set such as Dictionary and HashTable to save them. Basically, this method is similar to mode = "InProc. There is no locking problem, so there is no concurrency problem.
3. if you want to achieve a similar effect of mode = "StateServer", you can consider using memcached technology or write a simple service yourself and use one or more dictionaries internally, hashTable to save data. In this way, we can control the Read and Write timing more accurately. This method also requires the use of cookies to save session IDs.
4. If you want to achieve a similar effect of mode = "SQLServer", you can consider using technologies such as mongodb, And we can more accurately control the read/write time. This method also requires the use of cookies to save session IDs. If you have never used mongodb, you can refer to the blog: MongoDB practice development [zero-basic learning, with a complete Asp.net example]
From the above three alternative methods, if the Session is not used, the Cookie is required. In fact, cookies are designed to maintain the session state. It is not suitable for storing too much data. Therefore, it is appropriate to use it to store data such as session IDs. In fact, this is what Session does.
Recommended method: in order to maintain good scalability of website programs without storing too much session data, directly using cookies is the best choice.
Because the Cookie is stored in the browser and is not secure, we recommend that you only save simple data such as id and key. You can obtain the Cookie Based on the id and Key when you need other session data.
Here, I think I can answer the question in the title: Session is actually not necessary, and it is easy to save Session data without it.
Session in Asp.net MVC
Let's take a look at how Session is used in Asp.net MVC. As the underlying framework, the Asp.net platform provides HttpContext. the Session Member attribute allows us to conveniently use the Session. However, in MVC, the Controller abstract class also provides this attribute, we only need to access it (support for better testing ).
In retrospect, we can see that SessionStateModule decides whether to enable Session based on the current HttpHandler. But now the Controller and Page are separated. How does the Controller use Session? To answer this question, we need to talk about routing. Simply put, when MVC processes requests, the current HttpHandler is an example of the MvcHandler class, which is defined as follows:
Public class MvcHandler: IHttpAsyncHandler, IHttpHandler, IRequiresSessionState {
Therefore, in Controller. Session, it is the accessed HttpContext. Session, while MvcHandler implements the IRequiresSessionState interface. Therefore, you can access HttpContext. Session to obtain the Session. Note: The above code is taken from MVC 2.0. It can be seen from the type implementation interface that the Session will always be valid and cannot be closed, and it belongs to the pattern that affects concurrency. Therefore, you can only disable it globally from web. config.
Note: In MVC 3.0 and Asp.net 4.0, the access to custom Sessions of the Controller is supported.
In this way, if you do not want to continue using the Session, you can use the alternative method listed above.
In MVC, Session is also used, that is, the member attribute Controller. TempData. Usually we may use it like this:
TempData ["mydata"] = "aaaaaaaaaa"; // or other object <br/> return RedirectToAction ("Index"); <br/>
In this case, the data stored in TempData is actually stored in the Session. You can close the Session from web. config to see the exception. For this method, you can still use the previous method, but there is another method that can also be used as a substitute for Session. Let's take a look at the Controller code:
Protected virtual ITempDataProvider CreateTempDataProvider () {<br/> return new SessionStateTempDataProvider (); <br/>}< br/>
TempData supports other storage methods through this Provider method. In addition, there is a CookieTempDataProvider class available in MvcFutures. It's easy to use. Get the MVC source code, compile the project MvcFutures, reference it, and rewrite the above virtual method:
Protected override ITempDataProvider CreateTempDataProvider () <br/>{< br/> return new Microsoft. Web. Mvc. CookieTempDataProvider (this. HttpContext); <br/>}< br/>
Note that there are two traps: CookieTempDataProvider of MvcFutures of MVC 2 does not work properly. When I tried it, I found that it was written like this (I added the comments ):
Public static IDictionary <string, object> DeserializeTempData (string base64EncodedSerializedTempData) <br/>{< br/> byte [] bytes = Convert. fromBase64String (base64EncodedSerializedTempData); <br/> var memStream = new MemoryStream (bytes); <br/> var binFormatter = new BinaryFormatter (); <br/> return binFormatter. deserialize (memStream, null) as TempDataDictionary; // This will always return null <br/> // return binForm Atter. Deserialize (memStream, null) as IDictionary <string, object>; // you can write it in this way. <Br/>}< br/>
Even if it can run, this will lead to a large length of the generated Cookie, so it is easy to cause the browser to not support it.Finally, I overwrite the above Code (and another serialized code ):
Public static IDictionary <string, object> DeserializeTempData (string base64EncodedSerializedTempData) <br/>{< br/> try {<br/> return (new JavaScriptSerializer ()). deserialize <IDictionary <string, object> (<br/> HttpUtility. urlDecode (base64EncodedSerializedTempData); <br/>}< br/> catch {<br/> return null; <br/>}</p> <p> public static string SerializeToBase64EncodedString (IDictionary <string, object> values) <br/>{< br/> if (values = null | values. count = 0) <br/> return null; </p> <p> return HttpUtility. urlEncode (<br/> (new JavaScriptSerializer ()). serialize (values); <br/>}< br/>
Although the above method solves the problem of long serialization results, it also introduces a new problem: Because the IDictionary <string, object> type is used, as a result, complex types lose their type information during serialization. Therefore, during deserialization, the original type cannot be restored. For this reason, this method is only suitable for saving simple primitive data.
What should I do with the existing code?
Originally, this blog is no longer available here. Yes, the batch has been approved, and the solution has been given. What else can I say? However, I suddenly think of a very realistic problem. If someone asks me:Fish, I use Session in many parts of my code. If you follow the previous method, although it is feasible, there are a lot of code to be modified, and you need to test it and redeploy it, this is too heavy. Is there a better solution?
Yes, this is a real problem. What should we do?
To address this problem, I have also carefully thought about how to use the Session and what I have done with the Session. Generally, Session is used to save some temporary information related to users, and the possibility of Session conflicts for different pages is extremely small, use mode = "InProc. In fact, it is the Cache, which facilitates the association with the "current user.
To address this premise, continue to think: the biggest obstacle to overcome now is the locking of concurrency. As for this issue, we can refer to the descriptions in MSND above because of the GetItemExclusive methods. Here, it seems that there is a solution: I also come to implement a Provider using the Cache, and in the specific implementation, deliberately do not lock, it is not enough.
In the end, I provided two providers, all of which removed the lock-related operations. I tried it and the concurrency problem was not saved. However, it is worth noting that ProcCacheSessionStateStore uses Cache to save the Session content. Similar to mode = "InProc", CookieSessionStateStore uses cookies to save Session objects, but it has restrictions,Only suitable for saving simple primitive data (excluding sensitive information)The reason is the same as CookieTempDataProvider. Therefore, select the appropriate Provider based on your use scenario.
The following method is used: it is very easy to add the following configuration in web. config:
<SessionState mode = "Custom" customProvider = "CookieSessionStateStore"> <br/> <providers> <br/> <add name = "ProcCacheSessionStateStore" type = "Fish. sampleCode. procCacheSessionStateStore "/> <br/> <add name =" CookieSessionStateStore "type =" Fish. sampleCode. cookieSessionStateStore "/> <br/> </providers> <br/> </sessionState> <br/>
Now, you don't need to change the code. In the deployment environment, you only need to modify the configuration.
Warning the two providers I provide are only simple tests and have not passed the actual project test. If you need to use them, please test their availability on your own.
Click here to download all the sample code in this article