Writing lightweight Ajax component 01 – comparison with various implementation methods on webform platform

Time:2020-11-24

preface

Asp.net Webform and Asp.net MVC (MVC for short) is based on Asp.net One of them is that MVC pays more attention to the essence of HTTP, while webform tries to shield http. Therefore, it provides a large number of server controls and viewstate mechanism, so that developers can program based on event model just like developing windows form applications. Both have advantages and disadvantages and applicable scenarios, but there are many MVC now Asp.net The first choice for developers.

Webform is based on Asp.net On the basis of, Asp.net It provides enough extensibility, and we can also use these frameworks to write MVC under webform, which has the opportunity to write again. When it comes to webform, many people think of server control (drag control) In fact, otherwise, we can focus on HTML like MVC without using server controls at all. If webform wants to abandon server controls and focus on HTML, it is necessary to remove the < form runat = “server” > < / form > tag. The form of the runat server is the basis of its postback mechanism. Now that we’re going back to HTML + CSS + JS, it means that a lot of things have to be implemented on our own, such as handling Ajax requests. Unlike MVC, the design of webform starts with server control as the main component. If it is not used, it can only be implemented by its extensibility.

  This series is to implement a lightweight Ajax component based on webform platform, which is mainly divided into three parts:

1. Introduce various implementation methods of webform.

2. Analysis of ajaxpro components.

3. Write your own Ajax components.

1、 Introduction to Ajax

Asynchrony allows us to request or submit data like the server without refreshing the entire page. For complex pages, it is obviously inefficient to overload the whole page in order to request a little data. AJAX is to solve this problem. The core of AJAX is the XMLHttpRequest object, through which the request is submitted to the server in the form of text. After xmlhttprequest2.0, binary data submission is also supported.

Ajax security: for security reasons, AJAX is restricted by the same origin policy; that is, requests can only access the same domain and the same port, and cross domain requests will be rejected. Of course, sometimes requests need to be sent across domains. The common cross domain processing methods are CORS (cross domain resource sharing) and jsonp (parametric JSON).

Ajax data interaction format: Although the Ajax core object XMLHttpRequest has the word “XML”, the data exchange format between the client and the server is not limited to XML. For example, JSON format is more used now.

Ajax also has disadvantages. For example, the support of search engine is not very good; sometimes it will violate the original intention of URL resource location.

2 Asp.net Using Ajax on MVC platform

In MVC, AJAX is very convenient to call back-end methods. You just need to specify the name of action.

Front desk Code:


<body>
  <h1>index</h1>
  <input type="button" value="GetData" onclick="getData()" />
  <span></span>
</body>
<script type="text/javascript">
  function getData() {
    $.get("GetData", function (data) {
      $("#result").text(data);
    });
  }
</script>

Background code:


public class AjaxController : Controller
{
  public ActionResult GetData()
  {
    if(Request.IsAjaxRequest())
    {
      return Content("data");
    }
    return View();
  }
}

3、 Using Ajax on webform platform

  3.1 based on server control package or third party component

This is based on server controls, such as the Ajax toolkit toolkit, or components like fineui. Web front end is always composed of HTML + CSS + JS, but how to generate it. Native, we can write by ourselves or use some front-end plug-ins; those based on server controls are generated in the background, which is usually a little inefficient. The server component will generate a series of proxies in the foreground. The essence is the same, but the control encapsulates this process, and we don’t need to write it ourselves. The mode based on control or third-party component is very useful in some management systems. The access is not very large, so it can be developed quickly.

  3.2 based on ICallbackEventHandler interface

. net provides the ICallbackEventHandler interface to handle callback requests. The interface needs to use clientscriptmanager to generate proxy scripts in the foreground for sending and receiving requests, so the < form runat = “server” > tag is required.

Front desk Code:

<body>
  <form runat="server">
  <div>    
    < input type = "button" value = get callback result "onclick =" callserver() "/ >
    <span style="color:Red;"></span>
  </div>
  </form>
</body>
<script type="text/javascript">
  function getCallbackResult(result){
    document.getElementById("result").innerHTML = result;
  }
</script>

Background code:

public partial class Test1 : System.Web.UI.Page, ICallbackEventHandler
{    
  protected void Page_Load(object sender, EventArgs e)
  {
    //Client script manager
    ClientScriptManager scriptMgr = this.ClientScript;
 
    //Get the callback function. Getcallbackresult is the callback function
    string functionName = scriptMgr.GetCallbackEventReference(this, "", "getCallbackResult", "");
 
    //Call server is the execution function of the click button event
    string scriptExecutor = "function callServer(){" + functionName + ";}";
 
    //Registration script
    scriptMgr.RegisterClientScriptBlock(this.GetType(), "callServer", scriptExecutor, true);
  }
 
  //Interface method
  public string GetCallbackResult()
  {
    return "callback result";
  }
 
  //Interface method
  public void RaiseCallbackEvent(string eventArgument)
  {
  }
}

  This approach has the following disadvantages

1. The implementation is more complex, each page load event must register the corresponding script.

2. The foreground will generate a script file for the agent.

3. It is very troublesome to realize the complicated interaction of pages.

4. Although it is a callback, the page object is still generated.

3.3 using general processing procedures

The general handler is actually a class that implements the ihttphandler interface. Like the page class, it can also be used to process requests. The general handler is not usually used to generate HTML, and there is no complex event mechanism. Only one processrequest entry is used to process the request. We can write the Ajax request address as the path of the. Ashx file, which can be processed, and the efficiency is relatively high.

To output text content, just Response.Write For example, after getting the data from the database, it is serialized to a JSON format string, and then output. As mentioned above, the general handler does not generate HTML like a page. If you want to generate HTML, you can generate it by loading user controls. For example:


public void ProcessRequest(HttpContext context)
{
  Page page = new Page();
  Control control = page.LoadControl("~/PageOrAshx/UserInfo.ascx");
  if (control != null)
  {
    StringWriter sw = new StringWriter();
    HtmlTextWriter writer = new HtmlTextWriter(sw);
    control.RenderControl(writer);
    string html = sw.ToString();
    context.Response.Write(html);        
  }
}

The advantages of this method are lightweight and efficient; the disadvantage is that many ashx files need to be defined for more interaction, which increases the management and maintenance costs.

  3.4 page base class

The method of handling Ajax requests is defined in the page object, so that each page can focus on processing the related requests of this page. There’s a little bit of attention here.

  1. How to know that this request is an Ajax request?

By requesting x-requested- With:XMLHttlRequest It can be judged that most asynchronous requests of browsers will contain this request header; it can also be implemented by customizing the request header, for example: AjaxFlag:XHR 。

  2. Where is the unified treatment?

If it is very troublesome to judge and call in each page class, the process is transferred to a page base class.

 3. How to know which method is called?

It can be passed through parameters or defined in the request header, for example: M ethodName:GetData 。

  4. Know the method name, how to call it dynamically?

Reflection.

  5. How to know that the method can be called externally?

It can be thought that public type can be called externally or marked by tag attribute.

Through the above analysis, the simple implementation is as follows

Page base class:

public class PageBase : Page
{
  public override void ProcessRequest(HttpContext context)
  {
    HttpRequest request = context.Request;
    if (string.Compare(request.Headers["AjaxFlag"],"AjaxFlag",0) == 0)
    {
      string methodName = request.Headers["MethodName"];
      if (string.IsNullOrEmpty(methodName))
      {
        Endrequest ("methodname tag cannot be empty! "";
      }
      Type type = this.GetType().BaseType;
      MethodInfo info = type.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static);
      if (info == null)
      {
        Endrequest ("no suitable method call found! "";
      }        
      string data = info.Invoke(this, null) as string;
      EndRequest(data);
    }
    base.ProcessRequest(context);
  }
  private void EndRequest(string msg)
  {
    HttpResponse response = this.Context.Response;
    response.Write(msg);
    response.End();
  }
}

Page class:


public partial class Test1 : PageBase
{
  protected void Page_Load(object sender, EventArgs e)
  {
  }
  public string GetData()
  {
    return "213";
  }
}

Front desk Code:


function getData(){
  $.ajax({
    headers:{"AjaxFlag":"XHR","MethodName":"GetData"},
    success:function(data){
      $("#result").text(data);
    }
  });
}

4、 Optimized page base class

The above page base class has very little function, and it is inefficient to call through reflection. Here’s an optimization:

1. It can support simple type parameters.

For example, the above GetData can be GetData (string name). Relevant parameters can be obtained through function metadata, and then parameters can be set according to the requested parameters.

2. Add tag attribute.

Only attributes marked by Ajax methodattribute can be called externally.

3. Optimize reflection.

Using cache, avoid searching function information by function name every time.

Tag properties:


public class AjaxMethodAttribute : Attribute
{
}

Cache object:


public class CacheMethodInfo
{
  public string MethodName { get; set; }
  public MethodInfo MethodInfo { get; set; }
  public ParameterInfo[] Parameters { get; set; }
}

Base class code:

public class PageBase : Page
{
  private static Hashtable _ajaxTable = Hashtable.Synchronized(new Hashtable());
  public override void ProcessRequest(HttpContext context)
  {      
    HttpRequest request = context.Request;
    if (string.Compare(request.Headers["AjaxFlag"],"XHR",true) == 0)
    {
      InvokeMethod(request.Headers["MethodName"]);
    }
    base.ProcessRequest(context);
  }
  /// <summary>
  ///Reflection execution function
  /// </summary>
  /// <param name="methodName"></param>
  private void InvokeMethod(string methodName)
  {
    if (string.IsNullOrEmpty(methodName))
    {
      Endrequest ("methodname tag cannot be empty! "";
    }
    CacheMethodInfo targetInfo = TryGetMethodInfo(methodName);
    if (targetInfo == null)
    {
      Endrequest ("no suitable method call found! "";
    }
    try
    {
      object[] parameters = GetParameters(targetInfo.Parameters);
      string data = targetInfo.MethodInfo.Invoke(this, parameters) as string;
      EndRequest(data);
    }
    catch (FormatException)
    {
      Endrequest ("parameter type matching error! "";
    }
    catch (InvalidCastException)
    {
      Endrequest ("parameter type conversion error! "";
    }
    catch (ThreadAbortException)
    {
    }
    catch (Exception e)
    {
      EndRequest(e.Message);
    }
  }
  /// <summary>
  ///Get function metadata and cache it
  /// </summary>
  /// <param name="methodName"></param>
  /// <returns></returns>
  private CacheMethodInfo TryGetMethodInfo(string methodName)
  {
    Type type = this.GetType().BaseType;
    string cacheKey = type.AssemblyQualifiedName;
    Dictionary<string, CacheMethodInfo> dic = _ajaxTable[cacheKey] as Dictionary<string, CacheMethodInfo>;
    if (dic == null)
    {
      dic = new Dictionary<string, CacheMethodInfo>();
      MethodInfo[] methodInfos = (from m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
                    let ma = m.GetCustomAttributes(typeof(AjaxMethodAttribute), false)
                    where ma.Length > 0
                    select m).ToArray();
      foreach (var mi in methodInfos)
      {
        CacheMethodInfo cacheInfo = new CacheMethodInfo();
        cacheInfo.MethodName = mi.Name;
        cacheInfo.MethodInfo = mi;
        cacheInfo.Parameters = mi.GetParameters();
        dic.Add(mi.Name, cacheInfo);
      }
      _ajaxTable.Add(cacheKey, dic);
    }
    CacheMethodInfo targetInfo = null;
    dic.TryGetValue(methodName, out targetInfo);
    return targetInfo;
  }
  /// <summary>
  ///Get function parameters
  /// </summary>
  /// <param name="parameterInfos"></param>
  /// <returns></returns>
  private object[] GetParameters(ParameterInfo[] parameterInfos)
  {
    if (parameterInfos == null || parameterInfos.Length <= 0)
    {
      return null;
    }
    HttpRequest request = this.Context.Request;
    NameValueCollection nvc = null;
    string requestType = request.RequestType;
    if (string.Compare("GET", requestType, true) == 0)
    {
      nvc = request.QueryString;
    }
    else
    {
      nvc = request.Form;
    }
    int length = parameterInfos.Length;
    object[] parameters = new object[length];
    if (nvc == null || nvc.Count <= 0)
    {
      return parameters;
    }
    for (int i = 0; i < length; i++)
    {
      ParameterInfo pi = parameterInfos[i];
      string[] values = nvc.GetValues(pi.Name);
      object value = null;
      if (values != null)
      {
        if (values.Length > 1)
        {
          value = String.Join(",", values);
        }
        else
        {
          value = values[0];
        }
      }
      if (value == null)
      {
        continue;
      }
      parameters[i] = Convert.ChangeType(value, pi.ParameterType);
    }      
    return parameters;
  }
  private void EndRequest(string msg)
  {
    HttpResponse response = this.Context.Response;
    response.Write(msg);
    response.End();
  }
}

Page class:

public string GetData3(int i, double d, string str)
{
  string[] datas = new string[] { i.ToString(), d.ToString(), str };
  Return "the parameters are:+ String.Join (",", datas);
}

Front desk Code:


function getData3(){
  $.ajax({
    headers:{"AjaxFlag":"XHR","MethodName":"GetData3"},
    data:{"i":1,"d":"10.1a","str":"hehe"},
    success:function(data){
      $("#result").text(data);
    }
  });
}

5、 Summary

The above page base class already has the basic function, but it is not good enough. It mainly includes:

1. Attach to the page base class. For the original page base class, will undoubtedly become more complex. We want to separate it into a single component.

2. Efficiency. The efficiency of reflection is very low, especially in applications like web, which should be used with caution. Taking the dynamic execution function as an example, the main inefficiency lies in: a. the process of dynamically finding function according to the string. b. When executing a function, the internal reflection needs to package the parameters into an array, and then parse the parameters to the thread stack; before calling, the CLR also needs to check the correctness of the parameters, and then judge whether it has permission to execute. In fact, the above optimization is only half optimized, that is to say, it optimizes the search process, and the invocation will also have performance loss. Of course, with the higher version of. Net, the efficiency of reflection will also be improved, but this dynamic thing always exchanges efficiency for flexibility.

3. Complex parameters cannot be supported. Sometimes there are many parameters, and function parameters are usually encapsulated into an object type.

4. Ajax method attribute is just an empty tag attribute. We can add some functions to it. For example, mark the name of the function, whether to use session or not, and cache settings can be done here.

Friends who have used webform may mention the Ajax Pro component, which is an open source component. The next article will learn about this component through the source code, learn from its processing process, and analyze its advantages and disadvantages.