1. Client: Disable all submit buttons or controls after BTN submit. You can also add a transparent mask layer to block page elements.
2. SERVER: a better method is to create a new basic page class, which is used to replace the standard system. Web. UI. Page class. In this article, I will introduce several common functions and their general implementations, and load these functions into a new page class with richer functions. The features I want to introduce include:
Detects the F5 (refresh) Key.
Start and control a lengthy operation that needs to immediately send feedback pages to users.
Set the input focus when loading the page.
If you are familiar with ASP. net news groups and community sites, and read a large number of articles, books and press releases, you may already know how.. Net 1.x application implements the above functions in the context. The question here is how to provide all these features at the same time through a component and an entry point.
By defining a custom page class, you can provide all other functions and services on the new. ASPX page with minimal effort and get the maximum return. The Declaration of the included code page created using Visual Studio. NET 2003 is as follows:
public class WebForm1 :System.Web.UI.Page { : }
To make the web form class inherit a non-default page class, you only need to change the basic type as follows.
public class WebForm1 :Msdn.Page { : }
If the page is not created in Visual Studio. NET 2003 or embedded code is used, you can set the basic type for the page by using the inherits attribute in the @ page command.
<% @Page Inherits="Msdn.Page" ... %>
You can change the basic type of the ASP. NET page one by one as described here, or you can use the <pages> node in the configuration file.
<pages pageBaseType="Msdn.Page" />
<Pages> the pagebasetype attribute of a node indicates the name and assembly of the class to be used as the basic type of all dynamically created page classes. By default, this attribute is set to system. Web. UI. page in the machine. config file. You can overwrite the setting in the web. config file of the application.
Next let's take a look at how to implement the above functions in practical applications, and how to encapsulate these functions into a class that is all-encompassing.
Back to Top
Detect browser refresh
In an article published on aspnetPRO Magazine a few months ago, I outlined the steps required to detect the process when a user presses the F5 key in the browser to refresh the current page. Page refresh is the browser's response to specific user operations (Press F5 or click "refresh" toolbar button. Page refresh is an internal operation of the browser, because the browser does not send any external notifications for events or callbacks. Technically, page refresh is implemented by repeating the latest request in a "simple" way. In other words, the browser caches the latest processed requests and resends the processed requests when the user clicks the page refresh key.
Because all browsers (as far as I know) do not provide any type of notifications for page refresh events, the server-side code (for example, ASP.. NET, typical ASP, or isapi dll. To help ASP. NET detect and process page refreshes, you need to build an environment mechanism that makes two identical requests look different.
The browser refresh by resending the HTTP payload sent last time and make the copy look different from the original version (this is an additional service and requires additional parameters and ASP.. NET page must be able to cache these parameters ). Provides a detailed view of the subsystem I want to build.
Figure 1: Mechanism set to make the refresh request look different from the sending/submitting request
Each request processed in the Session Context obtains a unique and incremental ticket number. The ASP. NET page generates a ticket before a response is generated, and stores it in a custom hidden field and sends it to the browser. When a user submits a new request, which leads to the display of the page, the hidden field (if any) is automatically attached to the server request.
On the Web server, the new HTTP module intercepts the AcquireSessionState event, retrieves the current ticket from the hidden field, and compares it with the last processed ticket ID in the internal cache. (Tickets processed last time are stored in the session state .) If the current ticket is greater than the ID processed last time, or if both values are zero, the request is generally submitted or resent. In addition, the HTTP refresh module does not perform any other operations and sends requests intact.
If the last processed ticket is greater than or equal to the current ticket, the request is marked as a page refresh. In this case, the HTTP module creates a new entry only in the items set of the HTTP context of the request. In ASP. NET, The httpcontext object indicates the request context, which always exists throughout the request lifecycle. The items attribute of the httpcontext object is a set that can be used by the HTTP module, factory handler, and handler to forward custom information to the actual page object. All content stored in the items collection can be seen for all components involved in the process of processing the current request. The lifecycle of the information is the same as that of the request. Therefore, once a response is generated, all data will be sold out. By using the httpcontext. Current static attribute, you can access the HTTP context of the current request from any class involved in the process.
The HTTP refresh module creates a new entry named ispagerefreshed in the items set. The Boolean value indicates whether the request page is refreshed through normal submission/sending back request pages. The following list shows the implementation of the HTTP refresh module.
Using System; using System. web; using System. web. sessionState; namespace Msdn {public class RefreshModule: IHttpModule {// IHttpModule: Init public void Init (HttpApplication app) {// register the MPs event app. acquireRequestState + = new EventHandler (OnAcquireRequestState);} // IHttpModule: Dispose public void Dispose () {} // determine whether F5 or backward/forward operations are being processed private void OnAcquireRequestState (object sender, EventArgs e) {// access HTTP context HttpApplication app = (HttpApplication) sender; httpContext ctx = app. context; // check F5 operation RefreshAction. check (ctx); return ;}}}
The RefreshAction class contains the logic used to determine whether the current request is a page refresh. If it is determined that the page is refreshed, the Items set of HttpContext contains a new entry: IsPageRefreshed is set to true.
Public static void Check (HttpContext ctx) {// initialize the ticket field EnsureRefreshTicket (ctx); // read the ticket that was last processed in the Session (from the session) int lastTicket = GetLastRefreshTicket (ctx); // read the current requested ticket (from the hidden field) int thisTicket = GetCurrentRefreshTicket (ctx ); // compare two tickets if (thisTicket> lastTicket | (thisTicket = lastTicket & thisTicket = 0) {updatelstrefreshticket (ctx, thisTicket); ctx. items [PageRefreshEntry] = false;} else ctx. items [PageRefreshEntry] = true ;}
The names of hidden fields and session fields are set to public constants in the RefreshAction class and can be used outside the class.
How can an application page use this mechanism? When is page refresh really useful? The HTTP module does not block any requests. It only adds more information to the final ASP. NET page to process requests. The added information includes a Boolean value indicating page refresh.
Back to Top
Use Page refresh events
Web page users usually only perform a few operations, and to some extent, they are very happy to execute these operations. These operations include "back", "forward", "stop", and "refresh ". However, these operations constitute a standard toolkit for Internet browsers. Intercepting and segmenting these operations may bring some "limitations" to Internet operations that are generally recognized ". It may have a negative impact on users.
On the other hand, when you refresh the current page or return to the previously accessed page, the user will submit the processed requests to the server, which may interrupt the consistency of the application status. In this case, it may also have a negative impact on the application.
Consider the following:
You can use the DataGrid to display data and provide a button in each row to delete the data row. Although this is a common practice (click to delete the data implemented in the current application), it is extremely dangerous. It is easy for users to click the wrong button due to a mistake, which breaks down data consistency. If they refresh the page after deleting (intentionally or unintentionally, the second row is likely to be deleted.
When you refresh the page, the browser only repeats the last published content. From the perspective of ASP. NET Runtime Library, there is only one new request to be processed. The ASP. NET Runtime Library cannot distinguish between common requests and unexpected duplicate requests. If a record is deleted offline and deleted based on the position in the dataset stored in the memory, one more record may be deleted. If the last operation ends with insert, refresh the page and you may add a record.
These examples clearly expose some controversial design issues, but they reflect the full potential. So what is the best way to prevent page refreshing?
The Mechanism discussed earlier in this article can pre-process requests and determine whether the page is being refreshed. The information is passed to the page Handler through the httpcontext object. On the page, developers can use the following code to retrieve the data.
bool isRefresh = (bool) HttpContext.Current.Items["IsPageRefreshed"];
However, it is better to encapsulate data into a more easy-to-use attribute, that is, to encapsulate data into the ispagerefresh attribute, if you use a custom and more targeted page class.
public bool IsPageRefresh { get { object o = HttpContext.Current.Items[RefreshAction.PageRefreshEntry]; if (o == null) return false; return (bool) o; } }
By enabling the page class to inherit new and more functional basic classes (msdn. page in this example), you can learn the real cause of the request through the new attributes. The following example shows how to implement a key operation that should not be repeated during page refresh.
void AddContactButton_Click(object sender, EventArgs e) { if (!IsPageRefresh) AddContact(FName.Text, LName.Text); BindData(); TrackRefreshState(); }
The new contact is added only when the page is not refreshed. In other words, the contact is added only when the user clicks "Add-contact" as usual. The above code snippet has a very strange trackrefreshstate method. What is its function?
This method updates the ticket counter and ensures that the new page response contains hidden fields with the latest ticket. In this example, the next ticket is obtained by increasing the value stored in the session state. (The session state is used at will. It is best not to use the session state, but to use a more scalable Provider Model, just like in ASP. NET 2.0 .)
However, the trackrefreshstate method (which is intentionally named so that you can recall the trackviewstate method that you are more familiar with) should be explained. By calling this method, in addition to adding other information, you can also add hidden fields with the current request ticket to the page response. If no field is hidden (see figure 1), the refresh mechanism cannot detect whether the next sending request is refreshed or submitted. In other words, by calling trackrefreshstate In the event processing program, the system knows that you want to track this operation (and only this operation is tracked) to determine whether it is a page refresh. In this way, you only track page refreshes that may cause errors, and not all page refreshes will occur within the session lifecycle.
To use the page refresh function, you only need to add a new page in the Microsoft Visual Studio. NET project, open the code file, and change the basic class of the page to msdn. Page. Next, call the trackrefreshstate (New Public method of the msdn. Page class) when you perform an operation that should not be refreshed ). Use the new Boolean attribute ispagerefresh to check the refresh status.
Back to Top
Provides a pleasant experience during lengthy operations.
There is a question about how to track particularly time-consuming operations on the Web. We have provided various solutions through many articles and Multiple presentations. What I call "time-consuming operations" refers to all the operations in the progress bar in the Windows Forms solution. The progress bar displayed on the web page is prone to problems. The progress bar should be able to communicate with the server to obtain information that helps update the progress. In addition, this operation should not be completed by sending back or refreshing the metatag, so as not to completely refresh the page. In any situation, powerful dynamic HTML support is required.
To give users a pleasant experience during lengthy operations, a relatively simple method is to display an intermediate feedback page to show users some waiting messages, preferably with a dot animation. This page is completely irrelevant to the context, but it is undoubtedly more useful than displaying an hourglass on a blank page for a long time before loading a new page.
There is a simple and effective way to display some feedback during lengthy operations. It can be summarized into the following steps:
Once you click to start the task, the user is redirected to the feedback page. The feedback page must know the URL of the page where the task is actually executed. This URL (including session Status) can be transmitted through query strings or stored in accessible data storage.
After loading the feedback page, redirect to the work page. In this case, redirection is completed by scripts in the onload Javascript event of the page. The browser loads and displays the feedback page, and points to the work page. The page starts executing lengthy tasks and displays the feedback page for users.
As needed, the feedback page can be complex and contain many UI elements. It can contain a "please wait..." message or animated GIF, or use some dynamic HTML functions to display something that looks like a real progress bar.
I specially created a LengthyAction class to help you manage the beginning of a lengthy task.
Private const string UrlFormatString = "{0 }? Target = {1} "; public static void Start (string feedbackPageUrl, string targetPageUrl) {// URL of the prepared feedback page string url = String. format (UrlFormatString, feedbackPageUrl, targetPageUrl); // redirects the call to the feedback page HttpContext. current. response. redirect (url );}
This class features only one static method, namely Start. The Start method gets the URL of the feedback page and the target page (that is, the page where the task is executed. This method combines two parameters into a URL and redirects them.
The feedback page can contain any user interface you want, but must meet several key requirements. This page must be able to retrieve the name of the work page and provide a possible automatic mechanism to redirect to the work page through scripts. I have defined a custom basic Page class and built these functions into the class. In doing so, I must make some assumptions. In particular, my implementation assumes that the name of the work page is passed through the query string using the well-known property name target. The name of the target page is stored in the public attribute named TargetURL. In addition, the feedback page provides a function named GetAutoRedirectScript. The purpose of this function is to return the script code required for redirection through the script.
public string GetAutoRedirectScript() { return String.Format("location.href='{0}';", TargetUrl); }
To make the problem as simple as possible, the FeedbackBasePage class also looks for a common HTML control named Body. This is exactly the same as what you get from the following mark.
<body runat="server" id="Body">
If you can use a simple method to program the page body tag, the FeedbackBasePage class will find this method and automatically add the onload attribute; otherwise, you must manually add the onload attribute. This attribute is required to make the feedback page work normally.
HtmlGenericControl body = FindControl(BodyId) as HtmlGenericControl; if (body != null) body.Attributes["onload"] = GetAutoRedirectScript();
Finally, the markup Code provided to the browser is as follows.
<body onload="location.href='lengthyop.aspx'">
Let's take a look at the steps to implement lengthy operations using the classes discussed in this article.
Reference the required assembly, and then write the following event handler for the button that triggers the operation.
void ButtonLengthyOp_Click(object sender, EventArgs e) { LengthyAction.Start("feedback.aspx", "work.aspx"); }
Next, add the feedback page to the project. This is a regular Web form page. You can modify its <body> flag as described above and change the base class to FeedbackBasePage. After you click the button to start the process and before the result is displayed, the user interface on the feedback page is displayed, as shown in.
Figure 2: sequence of lengthy operations
In this example, I used a cross-page sending back, which is a more common solution for particularly lengthy operations. However, this raises the problem of passing the view status and the parameters required to complete the job on the work page. You can use the query string on the work page to connect the serialized object version, or store all the content in the ASP. NET cache or Session object. In this case, the HTTP context cannot be used, because this operation involves multiple HTTP requests, each of which has a different project set.
Note that the URL of the feedback page contains some details about the call, and may be as follows.
feedback.aspx?target=work.aspx?param1=123¶m2=hello
To hide these details, you can define a custom HTTP handler and bind it to a virtual URL that you think is more appropriate. The HTTP handler can retrieve the required information (including the name of the feedback page and work page) from the cache or session status ).
Back to Top
Set focus control
ASP. NET 2.0 provides an excellent new feature that allows you to specify which input control is set to focus when the page is displayed for the first time. This is a flexible function that reduces the burden of clicking start operations. For example, you can click Start to input data in the text box.
To specify the HTML component as the input focus, you need a short Javascript code. First, declare that this is not a cutting-edge rocket science. You can easily add this Javascript code as an embedded code to the onload attribute of the <body> tag. However, using the SetFocus method on the Page class to determine the name of the focus control on the server is indeed a huge step forward. In fact, you can use the following code in ASP. NET 2.0.
void Page_Load(object sender, System.EventArgs e) { SetFocus("TheFirstName"); }
When the page is displayed, the input control named TheFirstName becomes the focus. This method is convenient and effective, but how to encode it in ASP. NET 1.x?
Similarly, the skills for implementing this feature are already well known to the industry and can be easily searched from Google. But the problem is how to integrate it into the basic Page class for reuse.
Let's use the following declaration to extend the Msdn. Page basic class.
private string m_focusedControl; public void SetFocus(string ctlId) { m_focusedControl = ctlId; }
The SetFocus method collects control IDs and stores them in internal members. In the PreRender event of the page, call another helper function to build and insert Javascript code.
Private void AddSetFocusScript () {if (m_focusedControl = "") return; // Add a script to declare the StringBuilder sb = new StringBuilder (""); sb. append ("<script language = javascript>"); sb. append ("function"); sb. append (SetFocusFunctionName); sb. append ("(ctl) {"); sb. append ("if (document. forms [0] [ctl]! = Null) "); sb. append ("{document. forms [0] [ctl]. focus ();} "); sb. append ("}"); // Add a script to call the function sb. append (SetFocusFunctionName); sb. append ("('"); sb. append (m_focusedControl); sb. append ("'); <"); sb. append ("/"); // disconnect in this way to avoid misunderstanding... sb. append ("script>"); // register the script (name is case sensitive) if (! IsStartupScriptRegistered (SetFocusScriptName) RegisterStartupScript (SetFocusScriptName, sb. ToString ());}
Javascript code can be constructed like a dynamic string and accumulated in the StringBuilder object. The next step is to add the string to the page output. In ASP. NET, to add some client script code to the page, you must first register the code in a specific page-level collection. Therefore, the Page class provides several RegisterXxx methods. Each RegisterXxx method adds Javascript code blocks to different sets to insert them to different locations in the final page tag. For example, RegisterStartupScript inserts code before the close mark of the form. RegisterClientScriptBlock inserts script code after opening the form. Importantly, the script must contain two tags of the <script> element. Each script block is identified by a keyword, so that multiple server controls can use the same script block instead of sending it to the output stream twice or multiple times.
On the page, the following Javascript code block is inserted before the close mark of the form. In this way, it will start to run immediately after initialization.
<form> : <script language=javascript> function __setFocus(ctl) { if (document.forms[0][ctl] != null) { document.forms[0][ctl].focus(); } } __setFocus('TheFirstName'); </script> </form>
By using the SetFocus public method in the Msdn. Page class, you can determine the control that is used as the input focus when the Page is displayed in a browser at any location of the Page code. More importantly, you can make this decision based on runtime conditions and/or sending back events.