ホームページ ウェブフロントエンド jsチュートリアル 軽量の Ajax コンポーネントの作成 (詳細なグラフィックとテキストの説明)

軽量の Ajax コンポーネントの作成 (詳細なグラフィックとテキストの説明)

Apr 04, 2018 pm 05:07 PM
ajax グラフィックとテキスト 軽量

今回は軽量なajaxコンポーネントの書き方(画像と文章で詳しく解説)をお届けします。軽量なajaxコンポーネントを書く際の注意点について、実際の事例を紹介します。

これまでの説明で、ページ オブジェクトのメソッドを実行するための中心となるのはリフレクションであることがわかりました。リフレクションとは、リクエストからパラメーターを取得し、指定されたメソッドを実行するプロセスです。実際、これは ASP.NET MVC フレームワークの中心的な考え方に非常に似ており、URL を解析し、そこからコントローラーとアクション名を取得し、コントローラー オブジェクトをアクティブ化し、リクエストからアクション パラメーターを取得します。アクションを実行します。 Web フォーム プラットフォームでは、.aspx.cs にメソッドを記述します。目的は、ページ オブジェクトが生成される前に指定されたメソッドを実行し、結果を返すことです。

まず、実装後のいくつかの呼び出し例を見てみましょう。これらの関数は組み合わせて使用​​することもできます:

 [AjaxMethod]
    public void Test1(int index)
    {
      //简单调用
    }
    [AjaxMethod]
    public string Test2(Test test)
    {
      return "参数为一个Test实例";
    }
    [AjaxMethod(OutputCache = 20)]
    public string Test3(int index)
    {
      return "输出结果缓存20秒";
    }
    [AjaxMethod(ServerCache = 20)]
    public string Test4()
    {
      return "在服务端缓存20秒";
    }
    [AjaxMethod(SessionState=SessionState.None)]
    public void Test5()
    {
      //Session未被加载
    }
    [AjaxMethod(SessionState = SessionState.ReadOnly)]
    public void Test6()
    {
      //Session只能读不能写
    }
    [AjaxMethod(SessionState = SessionState.ReadWrite)]
    public void Test7()
    {
      //Session可以读写
    }
    [AjaxMethod(IsAsync = true)]
    public void Test8()
    {
      //异步调用
    }
ログイン後にコピー

基本的な実行プロセスについてはすでに理解したので、ここから直接トピックに進みます。

Ajax 規約

通常、主流ブラウザが ajax を使用して非同期リクエストを送信する場合、リクエスト ヘッダーには X-Requested-With:XMLHttpRequest というマークが付けられます。このタグを直接使用して Ajax リクエストであるかどうかを判断することもできますが、相互に影響を与えないように、プロジェクト内で他のコンポーネントが役立つ場合があります。これは次のとおりです:

internal static class AjaxConfig
 {
  /// <summary>
  /// 请求头Ajax标记键
  /// </summary>
  public const string Key = "AjaxFlag";
  /// <summary>
  /// 请求头Ajax标记值
  /// </summary>
  public const string Value = "XHR";
  /// <summary>
  /// 请求头Ajax方法标记
  /// </summary>
  public const string MethodName = "";
 }
ログイン後にコピー

http リクエスト ヘッダーに AjaxFlag: XHR が含まれている場合、それを処理する必要があることを意味します。さらに、http ヘッダーの MethodName は、実行するメソッドの名前を示します。

AjaxMethodAttribute mark属性

Mark属性はリフレクションに使用されます。ここで必要な関数をいくつか定義します。期待したいのは:

1.セッションステータスを設定できる

2.非同期ハンドラーをサポート

3.キャッシュの取得をサポート

4.サーバーサイドキャッシュをサポート

定義は以下の通りで、AttributeUsagでマークされたタグメソッドでのみ使用できます。

  /// <summary>
  /// ajax方法标记属性
  /// </summary>
  [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
  public class AjaxMethodAttribute : Attribute
  {
    public AjaxMethodAttribute()
    {      
    }
    private SessionState _sessionState = SessionState.None;
    private int _outputCache = 0;
    private int _serverCache = 0;
    private ContentType _contentType = ContentType.Plain;
    private bool _isUseAsync = false;
    /// <summary>
    /// session状态
    /// </summary>
    public SessionState SessionState 
    {
      get { return _sessionState; }
      set { _sessionState = value; }
    }
    /// <summary>
    /// 客户端缓存时间,以秒为单位。该标记只对get请求有效
    /// </summary>
    public int OutputCache 
    {
      get { return _outputCache; }
      set { _outputCache = value; }
    }
    /// <summary>
    /// 服务端缓存时间,以秒为单位
    /// </summary>
    public int ServerCache 
    {
      get { return _serverCache; }
      set { _serverCache = value; }
    }    
    /// <summary>
    /// 输出类型(默认为text/plain)
    /// </summary>
    public ContentType ContentType 
    {
      get { return _contentType; }
      set { _contentType = value; }
    }
    /// <summary>
    /// 使用启用异步处理
    /// </summary>
    public bool IsAsync 
    {
      get { return _isUseAsync; }
      set { _isUseAsync = value; }
    }
  }
  /// <summary>
  /// Session状态
  /// </summary>
  public enum SessionState
  {
    None,
    ReadOnly,
    ReadWrite    
  }
  /// <summary>
  /// 输出内容类型
  /// </summary>
  public enum ContentType
  {
    Plain,
    Html,
    XML,
    Javascript,
    JSON
  }
ログイン後にコピー

さまざまなハンドラーと AjaxHandlerFactory

前の記事によると、特定のハンドラーは主に非同期と非同期の 2 つのカテゴリに分けられ、これら 2 つのカテゴリでは、サポートされていないセッション状態のみがサポートされています。読み取り (IReadOnlySessionState インターフェイスの実装) と読み取りと書き込み (IRequiresSessionState インターフェイスの実装)。 IReadOnlySessionState と IRequiresSessionState は単なるマーク インターフェイスです (メソッドを使用しない場合は、マーク属性を使用して実装する方が合理的です)。非同期ハンドラーは IHttpAsyncHandler インターフェイスを実装する必要があり、このインターフェイスが IHttpHandler を実装します。 Handler の ProcessRequest メソッド (または BeginProcessRequest) でメソッドを実行します。定義は次のとおりです:

非同期状態のハンドラー:

 //不支持Session
  internal class SyncAjaxHandler : IHttpHandler
  {
    private Page _page;
    private CacheMethodInfo _cacheMethodInfo;
    internal SyncAjaxHandler(Page page, CacheMethodInfo cacheMethodInfo)
    {
      _page = page;
      _cacheMethodInfo = cacheMethodInfo;
    }
    public void ProcessRequest(HttpContext context)
    {
      //执行方法(下面详细介绍)
      Executor.Execute(_page, context, _cacheMethodInfo);
    }
    public bool IsReusable
    {
      get { return false; }
    }
    public static SyncAjaxHandler CreateHandler(Page page, CacheMethodInfo cacheMethodInfo, SessionState state)
    {
      switch (state)
      {
        case SessionState.ReadOnly:
          return new SyncAjaxSessionReadOnlyHandler(page, cacheMethodInfo);
        case SessionState.ReadWrite:
          return new SyncAjaxSessionHandler(page, cacheMethodInfo);
        default:
          return new SyncAjaxHandler(page, cacheMethodInfo);
      }
    }
  }
  //支持只读Session
  internal class SyncAjaxSessionReadOnlyHandler : SyncAjaxHandler, IReadOnlySessionState
  {
    internal SyncAjaxSessionReadOnlyHandler(Page page, CacheMethodInfo cacheMethodInfo)
      : base(page, cacheMethodInfo)
    {
    }
  }
  //支持读写Session
  internal class SyncAjaxSessionHandler : SyncAjaxHandler, IRequiresSessionState
  {
    internal SyncAjaxSessionHandler(Page page, CacheMethodInfo cacheMethodInfo)
      : base(page, cacheMethodInfo)
    {
    }
  }
ログイン後にコピー

非同期状態のハンドラー:

 //不支持Session
  internal class ASyncAjaxHandler : IHttpAsyncHandler, IHttpHandler
  {
    private Page _page;
    private CacheMethodInfo _cacheMethodInfo;
    internal ASyncAjaxHandler(Page page, CacheMethodInfo cacheMethodInfo)
    {
      _page = page;
      _cacheMethodInfo = cacheMethodInfo;
    }
    public IAsyncResult BeginProcessRequest(HttpContext context, AsyncCallback cb, object extraData)
    {
      //执行方法(下面详细介绍)
      Action<Page, HttpContext, CacheMethodInfo> action = new Action<Page, HttpContext, CacheMethodInfo>(Executor.Execute);
      IAsyncResult result = action.BeginInvoke(_page, context, _cacheMethodInfo, cb, action);
      return result;
    }
    public void EndProcessRequest(IAsyncResult result)
    {
      Action<Page, HttpContext, CacheMethodInfo> action = result.AsyncState as Action<Page, HttpContext, CacheMethodInfo>;
      action.EndInvoke(result);
    }
    public void ProcessRequest(HttpContext context)
    {
      throw new NotImplementedException();
    }
    public bool IsReusable
    {
      get { return false; }
    }
    public static ASyncAjaxHandler CreateHandler(Page page, CacheMethodInfo cacheMethodInfo, SessionState state)
    {
      switch (state)
      {
        case SessionState.ReadOnly:
          return new ASyncAjaxSessionReadOnlyHandler(page, cacheMethodInfo);
        case SessionState.ReadWrite:
          return new ASyncAjaxSessionHandler(page, cacheMethodInfo);
        default:
          return new ASyncAjaxHandler(page, cacheMethodInfo);
      }
    }
  }
  //支持只读Session
  internal class ASyncAjaxSessionReadOnlyHandler : ASyncAjaxHandler, IReadOnlySessionState
  {
    internal ASyncAjaxSessionReadOnlyHandler(Page page, CacheMethodInfo cacheMethodInfo)
      : base(page, cacheMethodInfo)
    {
    }
  }
  //支持读写Session
  internal class ASyncAjaxSessionHandler : ASyncAjaxHandler, IRequiresSessionState
  {
    internal ASyncAjaxSessionHandler(Page page, CacheMethodInfo cacheMethodInfo)
      : base(page, cacheMethodInfo)
    {
    }
  }
ログイン後にコピー

AjaxHandlerFactory は、リクエストに基づいて特定のハンドラーを生成するために使用される IHandlerFactory インターフェイスを実装します。Web に登録する必要があります。 .config。 AjaxHandlerFactory の GetHandler は、リクエストをインターセプトするための最初のステップです。リクエスト ヘッダーの AjaxFlag:XHR を使用して、処理する必要があるかどうかを判断し、必要な場合はハンドラーを作成します。それ以外の場合は、通常の方法で処理します。このメソッドは .aspx.cs で記述されているため、リクエストは .aspx サフィックスであり、これはページ (Page、IHttpHandler を実装) タイプです。ページは PageHandlerFactory を通じて作成されます。これは、IHandlerFactory インターフェイスも実装します。つまり、次の目的で使用されます。ハンドラーを作成します。したがって、IHttpHandler を作成するには PageHandlerFactory を使用する必要がありますが、PageHandlerFactory の constructor は、新しいものを直接作成できないため、CommonPageHandlerFactory を通じて継承する必要があります。

PageHandlerFactoryでPageを取得した後、メソッド名と組み合わせてリフレクションによりAjaxMethodAttributeのマーク属性を取得できます。次に、関連するプロパティに基づいて特定のハンドラーを生成します。具体的なコードは次のとおりです:

 internal class CommonPageHandlerFactory : PageHandlerFactory { }
  internal class AjaxHandlerFactory : IHttpHandlerFactory
  {
    public void ReleaseHandler(IHttpHandler handler)
    {
    }
    public IHttpHandler GetHandler(HttpContext context, string requestType, string url, string pathTranslated)
    {
      HttpRequest request = context.Request;
      if (string.Compare(request.Headers[AjaxConfig.Key], AjaxConfig.Value, true) == 0)
      {
        //检查函数标记
        string methodName = request.Headers[AjaxConfig.MethodName];
        if (methodName.IsNullOrEmpty())
        {
          Executor.EndCurrentRequest(context, "方法名称未正确指定!");
          return null;
        }
        try
        {          
          CommonPageHandlerFactory ajaxPageHandler = new CommonPageHandlerFactory();
          IHttpHandler handler = ajaxPageHandler.GetHandler(context, requestType, url, pathTranslated);
          Page page = handler as Page;
          if (page == null)
          {
            Executor.EndCurrentRequest(context, "处理程序类型必须是aspx页面!");
            return null;
          }
          return GetHandler(page, methodName, context);
        }
        catch
        {
          Executor.EndCurrentRequest(context, url + " 不存在!");
          return null;
        }
      }
      if (url.EndsWith(".aspx", StringComparison.CurrentCultureIgnoreCase))
      {
        CommonPageHandlerFactory orgPageHandler = new CommonPageHandlerFactory();
        return orgPageHandler.GetHandler(context, requestType, url, pathTranslated);
      }
      return null;
    }
    /// <summary>
    /// 获取自定义处理程序
    /// </summary>
    /// <param name="page">处理页面</param>
    /// <param name="methodName">处理方法</param>
    /// <param name="context">当前请求</param>
    private IHttpHandler GetHandler(Page page, string methodName, HttpContext context)
    {
      //根据Page和MethodName进行反射,获取标记属性(下面详细介绍)
      CacheMethodInfo methodInfo = Executor.GetDelegateInfo(page, methodName);
      if (methodInfo == null)
      {
        Executor.EndCurrentRequest(context, "找不到指定的Ajax方法!");
        return null;
      }
      AjaxMethodAttribute attribute = methodInfo.AjaxMethodAttribute;
      if (attribute.ServerCache > 0)
      {
        //先查找缓存
        object data = CacheHelper.TryGetCache(context);
        if (data != null)
        {
          Executor.EndCurrentRequest(context, data);
          return null;
        }
      }
      if (attribute.IsAsync)
      {
        //异步处理程序
        return ASyncAjaxHandler.CreateHandler(page, methodInfo, attribute.SessionState);
      }
      return SyncAjaxHandler.CreateHandler(page, methodInfo, attribute.SessionState);
    }
  }
ログイン後にコピー

  上面的CacheMethodInfo是用于缓存调用方法的相关信息的,第一篇我们有提到过优化缓存的一些方法,其中就包括缓存+委托。但这里我们并不直接缓存方法的MethodInfo,因为缓存MethodInfo的话,需要通过Invoke去执行,这样的效率比较低。这里我缓存的是方法的委托,该委托的签名为:Func,该委托的返回值为object类型,表示可以返回任意的类型(我们可以在组件内部进行处理,例如如果是引用类型(非string),就将其序列化为json,但这里并没有实现)。该委托接收两个参数,第一个参数是方法所属的对象,如果是静态方法就是null;第二个参数是方法的参数,定义为object[]表示可以接收任意类型的参数。通过委托执行方法,与直接调用方法的效率差别就不是很大(对委托不熟悉的朋友可以参见:委托)。CacheMethodInfo的定义如下:

 /// <summary>
  /// 缓存方法信息  
  /// </summary>
  sealed class CacheMethodInfo
  {
    /// <summary>
    /// 方法名称
    /// </summary>
    public string MethodName { get; set; }
    /// <summary>
    /// 方法委托
    /// </summary>
    public Func<object, object[], object> Func { get; set; }
    /// <summary>
    /// 方法参数
    /// </summary>
    public ParameterInfo[] Parameters { get; set; }
    /// <summary>
    /// Ajax标记属性
    /// </summary>
    public AjaxMethodAttribute AjaxMethodAttribute { get; set; }
  }
ログイン後にコピー

核心方法

1. Eexcutor.GetDelegateInfo 获取方法相关信息

  该方法用于遍历页面类,获取所有AjaxMethodAttribute标记的方法信息,生成一个CacheMethodInfo对象,包括标记信息、方法名称、参数信息,以及最重要的方法委托。该对象会缓存在一个哈希表中,下次获取时,直接从内存获得。

 /// <summary>
    /// 获取页面标记方法信息
    /// </summary>
    /// <param name="page">页面对象</param>
    /// <param name="methodName">方法名称</param>
    internal static CacheMethodInfo GetDelegateInfo(Page page, string methodName)
    {
      if (page == null)
      {
        throw new ArgumentNullException("page");
      }
      Type type = page.GetType();
      //ajaxDelegateTable是一个Hashtable
      Dictionary<string, CacheMethodInfo> dic = ajaxDelegateTable[type.AssemblyQualifiedName] as Dictionary<string, CacheMethodInfo>;
      if (dic == null)
      {
        dic = new Dictionary<string, CacheMethodInfo>();
        //遍历页面的所有MethodInfo
        IEnumerable<CacheMethodInfo> infos = (from m in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static)
                           let ca = m.GetCustomAttributes(typeof(AjaxMethodAttribute), false).FirstOrDefault()
                           where ca != null
                           select new CacheMethodInfo
                           {
                             //方法标记属性
                             AjaxMethodAttribute = ca as AjaxMethodAttribute,
                             //方法名称
                             MethodName = m.Name,
                             //方法参数信息
                             Parameters = m.GetParameters()
                           });
        if (infos.IsNullOrEmpty())
        {
          return null;
        }          
        for (int i = 0, length = infos.Count(); i < length; i++)
        {          
          CacheMethodInfo cacheMethodInfo = infos.ElementAt(i);          
          string name = cacheMethodInfo.MethodName;
          MethodInfo methodInfo = type.GetMethod(name);
          if (!dic.ContainsKey(name))
          {            
            //根据MethodInfo获取方法委托
            cacheMethodInfo.Func = ReflectionUtil.GetMethodDelegate(methodInfo);
            dic.Add(name, cacheMethodInfo);
          }
        }
        ajaxDelegateTable[type.AssemblyQualifiedName] = dic;
      }
      CacheMethodInfo currentMethodInfo = null;
      dic.TryGetValue(methodName, out currentMethodInfo);
      return currentMethodInfo;      
    }
ログイン後にコピー

  获取方法的委托的是通过一个ReflectionUtil获得的,该类主要用来优化反射,它通过Expression,可以将MethodInfo编译成Func委托,为Type编译一个Func委托,用于创建实例对象。

  通过Expression优化反射

  Expression(表达式树)允许我们将代码逻辑以表达式的形式存储在树状结构里,然后在运行时去动态解析,实现动态编辑和执行代码。熟悉ORM框架的朋友对Expression肯定很熟悉,因为大部分方法都有一个Expression类型的参数。访问关系型数据库的本质还是sql语句,orm的工作就是为开发人员屏蔽这个过程,以面向对象的方式去读写数据库,而不是自己编写sql语句。例如,Users.Where(u => u.Age > 18) 就可查询年龄大于18的用户。这里不对应用在orm的过程进行详解,下面我们介绍如何用Expression并利用它来生成委托。

  .net定义了许多表达式类型,这些类型都派生自Expression,Expression是一个抽象类,而且是一个工厂类,所有类型的表达式都通过它来创建。如图:

  先看一个 1 * 2 + 2 例子,我们用表达树来描述来描述它:

/*
       * a * b + 2 
       */
      /*
      直接操作
      int a = 1, b = 2;
      int result = a * 2 + 2;
      */
      /*
      通过委托调用
      Func<int, int, int> func = new Func<int, int, int>((a, b) => { return a * b + 2; });
      func(1, 2);
      */
      /*通过Expression调用*/
      //定义两个参数
      ParameterExpression pe1 = Expression.Parameter(typeof(int), "a");
      ParameterExpression pe2 = Expression.Parameter(typeof(int), "b");
      //定义一个常量
      ConstantExpression constExpression = Expression.Constant(2);      
      //参数数组
      ParameterExpression[] parametersExpression = new ParameterExpression[]{pe1,pe2};
      //一个乘法运算
      BinaryExpression multiplyExpression = Expression.Multiply(pe1, pe2);
      //一个加法运算
      BinaryExpression unaryExpression = Expression.Add(multiplyExpression, constExpression);
      //将上面的表达式转换为一个委托表达式
      LambdaExpression lambdaExpression = Expression.Lambda<Func<int, int, int>>(unaryExpression, parametersExpression);
      //将委托编译成可执行代码
      Func<int,int,int> func = lambdaExpression.Compile() as Func<int,int,int>;
      Console.WriteLine(func(1, 2));
ログイン後にコピー

  可以看到我们最终将其编译为一个具体类型的委托了。下面看我们真正用到的方法是如何实现的,代码如下:

 public static Func<object, object[], object> GetMethodDelegate(MethodInfo methodInfo)
    {
      if (methodInfo == null)
      {
        throw new ArgumentNullException("methodInfo");
      }
      //定义参数表达式,它表示委托的第一个参数
      ParameterExpression instanceExp = Expression.Parameter(typeof(object), "instance");
      //定义参数表达式,它表示委托的第二个参数
      ParameterExpression paramExp = Expression.Parameter(typeof(object[]), "parameters");
      //获取方法的参数信息数组
      ParameterInfo[] paramInfos = methodInfo.GetParameters();
      //参数表达式集合
      List<Expression> paramExpList = new List<Expression>();
      int length = paramInfos.Length;
      for (int i = 0; i < length; i++)
      {
        //获取paramExp参数数组的第i个元素
        BinaryExpression valueObj = Expression.ArrayIndex(paramExp, Expression.Constant(i));
        //将其转换为与参数类型一致的类型
        UnaryExpression valueCast = Expression.Convert(valueObj, paramInfos[i].ParameterType);
        //添加到参数集合
        paramExpList.Add(valueCast);
      }  
      //方法所属的实例的表达式,如果为静态则为null
      UnaryExpression instanceCast = methodInfo.IsStatic ? null : Expression.Convert(instanceExp, methodInfo.ReflectedType);
      //表示调用方法的表达式 
      MethodCallExpression methodCall = Expression.Call(instanceCast, methodInfo, paramExpList);
      //将表达式目录描述的lambda编译为可执行代码(委托)
      if (methodCall.Type == typeof(void))
      {
        Expression<Action<object, object[]>> lambda = Expression.Lambda<Action<object, object[]>>(methodCall, instanceExp, paramExp);
        Action<object, object[]> action = lambda.Compile();
        return (instance, parameters) =>
        {
          action(instance, parameters);
          return null;
        };
      }
      else
      {
        UnaryExpression castMethodCall = Expression.Convert(methodCall, typeof(object));
        Expression<Func<object, object[], object>> lambda = Expression.Lambda<Func<object, object[], object>>(castMethodCall, instanceExp, paramExp);
        return lambda.Compile();
      }
    }
ログイン後にコピー

  具体代码都有注释解释,最终我们获得了一个Func类型的委托,它会作为CacheMethodInfo的属性进行缓存。有兴趣测试反射性能的朋友,也不妨去测试对比一下这几种方式执行的效率差别:1.直接执行方法 2.Emit 3. 缓存+委托 4.Delegate.DynamicInvoke。

2. Executor.Execute 执行委托

  在执行委托前,我们需要先从请求获取参数,映射到方法。参数可以是简单的类型,如 string Test(int i,int j); 也可以是一个对象,如 string Test(User user); 如果是 string Test(User user1, User user2) 也行,提交参数时只需要加上 user1或 user2 前缀即可,例如 user1.Name,user2.Name。这里没有支持更多的匹配方式,像mvc,它还支持嵌套类型等等,这些可以自己去实现。如果参数是一个对象,我们可能需要为它的字段进行赋值,也可能为它的属性进行赋值。这里我们定义一个DataMember,用来表示字段或属性的父类。如:

  internal abstract class DataMember
  {
    public abstract string Name { get; }
    public abstract Type MemberType { get; }
    public abstract void SetValue(object instance,object value);
    public abstract object GetValue(object instance);
  }
ログイン後にコピー

  接着定义属性类型PropertyMember和字段类型FieldMember,分别继承了DataMember。

  PropertyMember定义:

  internal class PropertyMember : DataMember
  {
    private PropertyInfo property;
    public PropertyMember(PropertyInfo property)
    {
      if (property == null)
      {
        throw new ArgumentNullException("property");
      }
      this.property = property;
    }
    public override void SetValue(object instance, object value)
    {
      if (instance == null)
      {
        throw new ArgumentNullException("instance");
      }
      this.property.SetValue(instance, value, null);
    }
    public override object GetValue(object instance)
    {
      if (instance == null)
      {
        throw new ArgumentNullException("instance");
      }
      return this.property.GetValue(instance,null);
    }
    public override string Name
    {
      get { return this.property.Name; }
    }
    public override Type MemberType
    {
      get { return this.property.PropertyType; }
    }
  }
ログイン後にコピー

  FieldMember定义:

 internal class FieldMember : DataMember
  {
    private FieldInfo field;
    public FieldMember(FieldInfo field)
    {
      if (field == null)
      {
        throw new ArgumentNullException("field");
      }
      this.field = field;
    }
    public override void SetValue(object instance, object value)
    {
      if (instance == null)
      {
        throw new ArgumentNullException("instance");
      }
      this.field.SetValue(instance, value);
    }
    public override object GetValue(object instance)
    {
      if (instance == null)
      {
        throw new ArgumentNullException("instance");
      }
      return this.field.GetValue(instance);
    }
    public override string Name
    {
      get { return this.field.Name;}
    }
    public override Type MemberType
    {
      get { return this.field.FieldType; }
    }
  }
ログイン後にコピー

  定义一个DataMemberManager,用来遍历Type,获取所有字段和属性的,实现如下:

 internal static class DataMemberManager
  {
    /// <summary>
    /// 获取实例字段/属性集合
    /// </summary>
    /// <param name="type">类型</param>
    /// <returns></returns>
    public static List<DataMember> GetDataMember(Type type)
    {
      if (type == null)
      {
        throw new ArgumentNullException("type");
      }
      IEnumerable<PropertyMember> propertyMembers = from property in type.GetProperties(BindingFlags.Instance | BindingFlags.Public)
                         select new PropertyMember(property);
      IEnumerable<FieldMember> fieldMembers = from field in type.GetFields(BindingFlags.Instance | BindingFlags.Public)
                       select new FieldMember(field);
      List<DataMember> members = new List<DataMember>();
      foreach(var property in propertyMembers)
      {
        members.Add(property);
      }
      foreach (var field in fieldMembers)
      {
        members.Add(field);
      }
      return members;
    }
  }
ログイン後にコピー

  在前面我们定义的Handler的ProcessRequest方法中,我们调用了Executor.Execute,该方法用于执行委托,实现如下:

 /// <summary>
    /// 核心函数,执行Handler的方法
    /// </summary>
    /// <param name="page">页面对象</param>
    /// <param name="context">请求上下文</param>
    /// <param name="cacheMethodInfo">缓存方法原数据</param>
    internal static void Execute(Page page, HttpContext context, CacheMethodInfo methodInfo)
    {
      if (page == null)
      {
        throw new ArgumentNullException("page");
      }
      try
      {
        if (methodInfo != null)
        {
          HttpRequest request = context.Request;
          object[] parameters = GetParametersFromRequest(request, methodInfo.Parameters);
          object data = methodInfo.Func(page, parameters);
          int serverCache = methodInfo.AjaxMethodAttribute.ServerCache;
          if (serverCache > 0)
          {
            CacheHelper.Insert(context, methodInfo.AjaxMethodAttribute.ServerCache, data);
          }
          EndCurrentRequest(context, data, methodInfo.AjaxMethodAttribute.OutputCache);
        }
        else
        {
          EndCurrentRequest(context, "找不到合适的Ajax方法!");
        }
      }
      catch (FormatException)
      {
        EndCurrentRequest(context, "调用方法匹配到无效的参数!");
      }
      catch (InvalidCastException)
      {
        EndCurrentRequest(context, "参数转换出错!");
      }
      catch (System.Threading.ThreadAbortException)
      {
        //do nothing
      }
      catch (Exception ex)
      {
        EndCurrentRequest(context, ex.Message);
      }
    }
ログイン後にコピー

  CacheMethodInfo我们已经获得了,现在只要获得参数我们就可以执行方法。

  GetParameterFromRequest用于从请求获取object[]参数数组。根据上面所说的,如果参数是一个简单类型,那么直接进行转换;如果是实例对象,那么我们先要创建new一个实例对象,然后为其字段或属性赋值。实现如下:

 /// <summary>
    /// 从请求获取参参数
    /// </summary>
    /// <param name="request">HttpRequest</param>
    ///<param name="parameters">参数信息</param>
    /// <returns>参数数组</returns>
    private static object[] GetParametersFromRequest(HttpRequest request, ParameterInfo[] parameters)
    {
      if (parameters.IsNullOrEmpty())
      {
        return null;
      }
      int length = parameters.Length;
      object[] realParameters = new object[length];
      for (int i = 0; i < length; i++)
      {
        ParameterInfo pi = parameters[i];
        Type piType = pi.ParameterType.GetRealType();
        object value = null;
        if (piType.IsValueType())
        {
          //值类型
          value = ModelUtil.GetValue(request, pi.Name, piType);
          value = value ?? Activator.CreateInstance(piType);
        }
        else if (piType.IsClass)
        {
          //引用类型
          object model = ModelUtil.CreateModel(piType);
          ModelUtil.FillModelByRequest(request, pi.Name, piType, model);
          value = model;
        }
        else
        {
          throw new NotSupportedException(pi.Name + " 参数不被支持");
        }
        realParameters[i] = value;
      }
      return realParameters;
    }
ログイン後にコピー

  ModelUtil会从Http Request获取参数,并进行类型转换处理:

 internal static class ModelUtil
  {
    /// <summary>
    /// 缓存构造函数
    /// </summary>
    private static Hashtable constructorTable = Hashtable.Synchronized(new Hashtable());
 
    /// <summary>
    /// 根据名称从HttpRequest获取值
    /// </summary>
    /// <param name="request">HttpRequest</param>
    /// <param name="name">键名称</param>
    /// <param name="type">参数类型</param>
    /// <returns></returns>
    public static object GetValue(HttpRequest request, string name, Type type)
    {
      string[] values = null;
      if (string.Compare(request.RequestType, "POST", true) == 0)
      {
        values = request.Form.GetValues(name);
      }
      else
      {
        values = request.QueryString.GetValues(name);
      }
      if (values.IsNullOrEmpty())
      {
        return null;
      }
      string data = values.Length == 1 ? values[0] : string.Join(",", values);
      return Convert.ChangeType(data, type);
    }
    /// <summary>
    /// 创建实例对象
    /// </summary>
    /// <param name="type">实例类型</param>
    /// <returns></returns>
    public static object CreateModel(Type type)
    {
      if (type == null)
      {
        throw new ArgumentNullException("type");
      }
      Func<object> func = constructorTable[type.AssemblyQualifiedName] as Func<object>;
      if (func == null)
      {  
        func = ReflectionUtil.GetConstructorDelegate(type);
        constructorTable[type.AssemblyQualifiedName] = func;
      }
      if (func != null)
      {
        return func();
      }
      return null;
    }
    /// <summary>
    /// 填充模型
    /// </summary>
    /// <param name="request">HttpRequest</param>
    /// <param name="name">键名称</param>
    /// <param name="prefix">参数类型</param>
    /// <parparam name="model">实例对象</parparam>
    public static void FillModelByRequest(HttpRequest request, string name, Type type, object model)
    {
      if (model == null)
      {
        return;
      }
      IEnumerable<DataMember> members = DataMemberManager.GetDataMember(type);
      if (members.IsNullOrEmpty())
      {
        return;
      }
      object value = null;
      foreach (DataMember member in members)
      {
        value = GetValue(request, string.Format("{0}.{1}", name, member.Name), member.MemberType);
        value = value ?? GetValue(request, member.Name, member.MemberType);
        member.SetValue(model, value);
      }
    }
  }
ログイン後にコピー

  如果是引用类型,需要通过构造函数创建对象,像前面用于,这里我们也用Expression来构建一个Func类型的委托来优化,它调用了ReflectionUtil.GetConstructorDelegate方法。实现如下:

  /// <summary>
    /// 获取构造函数委托
    /// </summary>
    /// <param name="type">实例类型</param>
    /// <returns></returns>
    public static Func<object> GetConstructorDelegate(Type type)
    {
      if (type == null)
      {
        throw new ArgumentNullException("type");
      }
      ConstructorInfo ci = type.GetConstructor(Type.EmptyTypes);
      if (ci == null)
      {
        throw new MissingMemberException("类型必须有一个无参public构造函数!");
      }
      NewExpression newExp = Expression.New(type);
      Expression<Func<object>> lambda = Expression.Lambda<Func<object>>(newExp);
      return lambda.Compile();
    }
ログイン後にコピー

  最后再输出结果时,如果是Get请求,并且需要缓存,我们还需要设置一下Response.Cache。如下:

    private static void EndRequest(HttpContext context, object data, int outPutCache, ContentType contentType)
    {
      HttpResponse response = context.Response;
      if (outPutCache != 0)
      {
        if (string.Compare(context.Request.HttpMethod, "GET", true) == 0)
        {
          if (outPutCache > 0)
          {
            response.Cache.SetCacheability(HttpCacheability.Public);
            response.Cache.SetMaxAge(new TimeSpan(0, 0, outPutCache));
            response.Cache.SetExpires(DateTime.Now.AddSeconds(outPutCache));
          }
          else
          {
            response.Cache.SetCacheability(HttpCacheability.NoCache);
            response.Cache.SetNoStore();
          }
        }
      }
      response.ContentType = GetContentType(contentType);
      response.ContentEncoding = System.Text.Encoding.UTF8;
      if (data != null)
      {
        response.Write(data);
      }
      response.End();
    }
ログイン後にコピー

相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!

推荐阅读:

Ajax请求async有哪些方式?应该如何使用

FileUpload实现单个文件的上传

以上が軽量の Ajax コンポーネントの作成 (詳細なグラフィックとテキストの説明)の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

ローエンドまたは古いコンピュータに最適な軽量 Linux ディストリビューション ローエンドまたは古いコンピュータに最適な軽量 Linux ディストリビューション Mar 06, 2024 am 09:49 AM

古いコンピュータやローエンドのコンピュータに新しい命を吹き込むのに最適な Linux ディストリビューションをお探しですか? 「はい」の場合は、正しい場所に来ています。この記事では、特に古いハードウェアや性能の低いハードウェア向けにカスタマイズされた軽量 Linux ディストリビューションのおすすめをいくつか紹介します。その背後にある動機が老朽化したデバイスを復活させることであっても、単に予算内でパフォーマンスを最大化することであっても、これらの軽量オプションは確実に要件に適合します。軽量の Linux ディストリビューションを選択する理由は何ですか?軽量の Linux ディストリビューションを選択することにはいくつかの利点があります。その 1 つは、最小限のシステム リソースで最高のパフォーマンスが得られることです。そのため、処理能力、RAM、ストレージ容量が限られている古いハードウェアに最適です。さらに、リソースを大量に消費するため、

jQuery AJAX リクエストで発生した 403 エラーを解決する方法 jQuery AJAX リクエストで発生した 403 エラーを解決する方法 Feb 20, 2024 am 10:07 AM

タイトル: jQuery AJAX リクエストの 403 エラーを解決する方法とコード例。403 エラーは、サーバーがリソースへのアクセスを禁止するリクエストを指します。このエラーは通常、リクエストにアクセス許可がないか、サーバーによって拒否されたために発生します。 jQueryAJAX リクエストを行うときにこのような状況に遭遇することがありますが、この記事ではこの問題の解決方法とコード例を紹介します。解決策: 権限を確認します。まず、要求された URL アドレスが正しいことを確認し、リソースにアクセスするための十分な権限があることを確認します。

jQuery AJAXリクエスト403エラーを解決する方法 jQuery AJAXリクエスト403エラーを解決する方法 Feb 19, 2024 pm 05:55 PM

jQuery は、クライアント側の開発を簡素化するために使用される人気のある JavaScript ライブラリです。 AJAX は、Web ページ全体をリロードせずに、非同期リクエストを送信し、サーバーと対話するテクノロジーです。ただし、jQuery を使用して AJAX リクエストを行うと、403 エラーが発生することがあります。 403 エラーは通常、セキュリティ ポリシーまたは権限の問題が原因で、サーバーによってアクセスが拒否されたエラーです。この記事では、jQueryAJAX リクエストで 403 エラーが発生した場合の解決方法について説明します。

なぜ Golang が高同時処理に適しているのかを分析してください。 なぜ Golang が高同時処理に適しているのかを分析してください。 Feb 29, 2024 pm 01:12 PM

Golang (Go 言語) は、Google によって開発されたプログラミング言語で、効率的、簡潔、同時実行、軽量のプログラミング エクスペリエンスを提供することを目的としています。同時実行機能が組み込まれており、同時実行性が高い状況でもパフォーマンスを向上させる強力なツールを開発者に提供します。この記事では、Golang が高同時処理に適している理由を詳しく説明し、具体的なコード例を示して説明します。 Golang の同時実行モデル Golang は、ゴルーチンとチャネルに基づく同時実行モデルを採用しています。五郎

Ajaxを使用してPHPメソッドから変数を取得するにはどうすればよいですか? Ajaxを使用してPHPメソッドから変数を取得するにはどうすればよいですか? Mar 09, 2024 pm 05:36 PM

Ajax を使用して PHP メソッドから変数を取得することは、Web 開発では一般的なシナリオであり、Ajax を使用すると、データを更新せずにページを動的に取得できます。この記事では、Ajax を使用して PHP メソッドから変数を取得する方法と、具体的なコード例を紹介します。まず、Ajax リクエストを処理し、必要な変数を返すための PHP ファイルを作成する必要があります。以下は、単純な PHP ファイル getData.php のサンプル コードです。

jQuery AJAX エラー 403 の問題を解決するにはどうすればよいですか? jQuery AJAX エラー 403 の問題を解決するにはどうすればよいですか? Feb 23, 2024 pm 04:27 PM

jQueryAJAX エラー 403 の問題を解決するにはどうすればよいですか? Web アプリケーションを開発する場合、非同期リクエストを送信するために jQuery がよく使用されます。ただし、jQueryAJAX の使用時に、サーバーによってアクセスが禁止されていることを示すエラー コード 403 が発生する場合があります。これは通常、サーバー側のセキュリティ設定が原因ですが、回避する方法があります。この記事では、jQueryAJAX エラー 403 の問題を解決する方法と具体的なコード例を紹介します。 1.作る

PHP と Ajax: オートコンプリート提案エンジンの構築 PHP と Ajax: オートコンプリート提案エンジンの構築 Jun 02, 2024 pm 08:39 PM

PHP と Ajax を使用してオートコンプリート候補エンジンを構築します。 サーバー側スクリプト: Ajax リクエストを処理し、候補を返します (autocomplete.php)。クライアント スクリプト: Ajax リクエストを送信し、提案を表示します (autocomplete.js)。実際のケース: HTML ページにスクリプトを組み込み、検索入力要素の識別子を指定します。

PHP と Ajax: 動的に読み込まれるコンテンツを作成するためのソリューション PHP と Ajax: 動的に読み込まれるコンテンツを作成するためのソリューション Jun 06, 2024 pm 01:12 PM

Ajax (非同期 JavaScript および XML) を使用すると、ページをリロードせずに動的コンテンツを追加できます。 PHP と Ajax を使用すると、製品リストを動的にロードできます。HTML はコンテナ要素を含むページを作成し、Ajax リクエストはロード後に要素にデータを追加します。 JavaScript は Ajax を使用して XMLHttpRequest を通じてサーバーにリクエストを送信し、サーバーから JSON 形式で商品データを取得します。 PHP は MySQL を使用してデータベースから製品データをクエリし、それを JSON 形式にエンコードします。 JavaScript は JSON データを解析し、ページ コンテナーに表示します。ボタンをクリックすると、製品リストをロードするための Ajax リクエストがトリガーされます。

See all articles