📜 ⬆️ ⬇️

Traffic minimization in ASP.NET Web Forms, clickable div and periodic server polling

ASP.NET Web Forms technology is slowly but surely becoming a thing of the past. It is replaced by Web API with Angular 6 and similar stacks. But I inherited the project on Web Forms with a huge legacy. I have a few friends who have a plus or minus similar situation. Long written applications on old technology that need to be developed and maintained. Web Forms has the ability to not update the entire page on PostBack, but only a part of it. What is wrapped in UpdatePanel. This adds interactivity, but it still works rather slowly and consumes a lot of traffic, because rendering takes place every time on the server, and the ready markup is sent to the client, which needs to be inserted instead of the current div. By the way, UpdatePanel is just rendered in a div, in which the markup is then replaced.

What can I do to minimize traffic?

  1. Write WebMethod on the page and call it from the client using AJAX, when receiving a response, change the DOM via JS.

    The disadvantage of this solution is that WebMethod cannot be identified in the control. I do not want to write all the functionality on the page, especially if it is used several times on different pages.
  2. Write an asmx service, and call it from the client. This is better, but in this case there is no explicit connection between the control and the service. The number of services will grow with an increase in the number of controls. In addition, ViewState will not be available to us, which means that we will pass the parameters explicitly when accessing the service, so we will do server validation and check if the user has the right to do what he requested.
  3. Use the ICallbackEventHandler interface. This is in my opinion a pretty good option.
    ')
    I will dwell on it in more detail.

The first thing to do is to inherit our UserControl from ICallbackEventHandler and write the methods RaiseCallbackEvent and GetCallbackResult. It is a little strange that there are 2. The first is responsible for receiving parameters from the client, the second is for returning the result.
It will look like this

public partial class SomeControl : UserControl, ICallbackEventHandler { #region  /// <summary> ///    /// </summary> private Guid _someFileId; #endregion #region  ICallbackEventHandler /// <inheritdoc /> public void RaiseCallbackEvent(string eventArgument) { //    try { dynamic args = JsonConvert.DeserializeObject<dynamic>(eventArgument); _someFileId = (Guid) args.SomeFileId; string type = (string) args.Type; } catch (Exception exc) { //  throw; } } /// <inheritdoc /> public string GetCallbackResult() { //  try { // -  return JsonConvert.SerializeObject(new { Action = actionName, FileId = _someFileId, }); } catch (Exception exc) { //  throw; } } #endregion } 

It was the server side. Now client

 var SomeControl = { _successCallbackHandler: function (responseData) { let data = JSON.parse(responseData); switch (data.Action) { case "continue": // -   break; case "success": //  -  break; case "fail": //   break; default: //   alert,     alert("      "); break; } }, _failCallbackHandler: function() { alert("      "); }, } 

That's not all. We still need to generate JS to bind all our functions.

  protected override void OnLoad(EventArgs e) { base.OnLoad(e); //   SomeControl.js,     Page.ClientScript.RegisterClientScriptInclude(Page.GetType(), "SomeControl", "/Scripts/controls/SomeControl.js?v=2.24.0"); string callbackArgument = //   //***  .***   JS   SomeControl  CallServer.       ,     ScriptManager.RegisterStartupScript(Page, Page.GetType(), "SomeControl.Initialize", $@"SomeControl.CallServer = function(someFileId) {{ let args = JSON.stringify({{ SomeFileId : someFileId, Type: '{callbackArgument}' }}); {Page.ClientScript.GetCallbackEventReference(this, "args", "SomeControl._successCallbackHandler", string.Empty, "SomeControl._failCallbackHandler", true)}; }};", true); //    ScriptManager.GetCurrent(Page)?.RegisterAsyncPostBackControl(this); } 

This is obviously the code behind control.

The most interesting is the generation of the JS function by the method GetCallbackEventReference.

Pass into it


How will it all work in a bundle?

From JS we can call SomeControl.CallServer, this function will create a local variable args and pass control to the function that will make the request to the server via AJAX.
Further control is transferred to the server method RaiseCallbackEvent. All that was in the client variable args is now in the eventArgument server input parameter.
After the execution of RaiseCallbackEvent, the control will be transferred to GetCallbackResult.

The string that we will return via return will be sent to the client and will get into the input parameter of the SomeControl._successCallbackHandler function, that is, in responseData.
If at some stage the server code gives Exception, then control will be transferred to the client SomeControl._failCallbackHandler

More needs to be said about ViewState. ViewState is transferred from the client to the server, and it can be used, but only in ReadOnly mode, since back to the client ViewState is not sent.

The design is confusing at first glance, but if you look at it, it turns out to be quite convenient, and the traffic saved.

The second question that I want to highlight in this article is clickable divs or how you can trigger an update UpdatePanel from the client.

Why do we need clickable divs, can we just use <asp: Button>?
I like the fact that the div can be drawn up as I want, I am not limited to the input type = "button"

For implementation, you need to inherit from the IPostBackEventHandler interface.

He has only 1 method

 public void RaisePostBackEvent(string eventArgument) 

Now, as in the previous case, we need to generate a JS to call this method.

It looks like this

 Page.ClientScript.GetPostBackEventReference(this, callbackArgument) 

callbackArgument is set on the server and it will not work to change it on the client. But you can always put something in the HiddenField. We have a full PostBack

Now the result of the execution of GetPostBackEventReference can be hung on onclick of any div or span or anything at all.

Or just call from JS by timer.

Be sure to register the control as asynchronous (on OnLoad we call

 ScriptManager.GetCurrent(Page)?.RegisterAsyncPostBackControl(this); 
), otherwise even being inside UpdatePanel a synchronous PostBack will be called and the whole page will be updated, and not just the contents of UpdatePanel

Using the 2 methods described above, I implemented, for example, such a scenario.

The user pressed a button, a small request for a lengthy operation (10-15 seconds) went to the server, the user received a short response, when parsing which the client script calls setTimeout. The setTimeout function is passed a callback function to the server in order to learn about the results of the operation previously requested. If the result is ready, then we call PostBack in UpdatePanel - the update given by UpdatePanel is updated. If the result is not yet ready, then call setTimeout again.

Anyone who is still working with Web Forms - good luck, I hope the article will make your systems faster and more beautiful, and users will say thank you.

Source: https://habr.com/ru/post/427291/


All Articles