热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

通过实例模拟ASP.NETMVC的Model绑定机制[上篇]

总的来说,针对目标Action方法参数的Model绑定完全由组件ModelBinder来实现,在默认情况下使用的ModelBinder类型为DefaultModelBinder,接下来我们将按照逐层深

总的来说,针对目标Action方法参数的Model绑定完全由组件ModelBinder来实现,在默认情况下使用的ModelBinder类型为DefaultModelBinder,接下来我们将按照逐层深入的方式介绍实现在DefaultModelBinder的默认Model绑定机制。[源代码从这里下载][本文已经同步到《How ASP.NET MVC Works?》中]

目录 
一、简单类型 
二、复杂类型 
三、数组 
四、集合 
五、字典

一、简单类型

对于旨在绑定目标Action方法参数值的Model来说,最简单的莫过于简单参数类型的情况。通过《初识Model元数据》的介绍我们知道,复杂类型和简单类型之间的区别仅仅在于是否支持针对字符串类型的转换。由于参数值的数据源在请求中以字符串的形式存在,对于支持字符串转换的简单类型来说,可以直接通过类型转换得到参数值。我们通过一个简单的实例来模拟实现在DefaultModelBinder中针对简单类型的Model绑定。如下所示的是我们自定义的DefaultModelBinder,其属性ValueProvider用于从请求中提供相应的数据值,该属性在构造函数中被初始化。

   1: public class DefaultModelBinder
   2: {
   3:     public IValueProvider ValueProvider { get; private set; }
   4:     public DefaultModelBinder(IValueProvider valueProvider)
   5:     {
   6:         this.ValueProvider = valueProvider;
   7:     }
   8:  
   9:      public IEnumerable<object> GetParameterValues(ActionDescriptor actionDescriptor)
  10:     {
  11:         foreach (ParameterDescriptor parameterDescriptor in actionDescriptor.GetParameters())
  12:         {
  13:             string prefix = parameterDescriptor.BindingInfo.Prefix ?? parameterDescriptor.ParameterName;
  14:             yield return GetParameterValue(parameterDescriptor, prefix);
  15:         }
  16:     }
  17:  
  18:     public object GetParameterValue(ParameterDescriptor parameterDescriptor, string prefix)
  19:     {
  20:         object parameterValue = BindModel(parameterDescriptor.ParameterType, prefix);
  21:         if (null == parameterValue && string.IsNullOrEmpty(parameterDescriptor.BindingInfo.Prefix))
  22:         {
  23:             parameterValue = BindModel( parameterDescriptor.ParameterType, "");
  24:         }
  25:         return parameterValue ?? parameterDescriptor.DefaultValue;
  26:      }
  27:  
  28:     public object BindModel(Type parameterType, string prefix)
  29:     {
  30:         if (!this.ValueProvider.ContainsPrefix(prefix))
  31:         {
  32:             return null;
  33:         }
  34:         return this.ValueProvider.GetValue(prefix).ConvertTo(parameterType);
  35:     }
  36: }

方法GetParameterValues根据指定的用于描述Action方法的ActionDescriptor获取最终执行该方法的所有参数值。在该方法中,我们通过调用ActionDescriptor的GetParameters方法得到用于描述其参数的所有ParameterDescriptor对象,并将每一个ParameterDescriptor作为参数调用GetParameterValue方法得到具体某个参数的值。GetParameterValue除了接受一个类型为ParameterDescriptor的参数外,还接受一个用于表示前缀的字符串参数。如果通过ParameterDescriptor的BindingInfo属性表示的ParameterBindingInfo对象具有前缀,则采用该前缀;否则采用参数名称作为前缀。

对于GetParameterValue方法来说,它又通过调用另一个将参数类型作为参数的BindModel方法来提供具体的参数值,BindModel方法同样接受一个表示前缀的字符串作为其第二个参数。GetParameterValue最初将通过ParameterDescriptor获取到的参数值和前缀作为参数调用BindModel方法,如果返回值为Null并且参数并没有显示执行前缀,会传入一个空字符串作为前缀再一次调用BindModel方法,这实际上模拟了之前提到过的去除前缀的后备Model绑定机制(针对于ModelBindingContext的FallbackToEmptyPrefix属性)。如果最终得到的对象不为Null,则将其作为参数值返回;否则返回参数的默认值。

BindModel方法的逻辑非常简单。先将传入的前缀作为参数调用ValueProvider的ContainsPrefix方法判断当前的ValueProvider保持的数据是否具有该前缀。如果返回之为False,直接返回Null,否则以此前缀作为Key调用GetValue方法得到一个ValueProviderResult调用,并最终调用ConvertTo方法转换为参数类型并返回。

为了验证我们自定义的DefaultModelBinder能够真正地用于针对简单参数类型的Model绑定没我们将它应用到一个具体的ASP.NET MVC应用中。在通过Visual Studio的ASP.NET MVC项目模板创建的空Web应用中,我们创建了如下一个默认的HomeController。HomeController具有一个ModelBinder属性,其类型正是我们自定义的DefaultModelBinder,该属性通过方法GetValueProvider提供。

   1: public class HomeController : Controller
   2: {
   3:     public DefaultModelBinder ModelBinder { get; private set; }
   4:     public HomeController()
   5:     {
   6:         this.ModelBinder = new DefaultModelBinder(GetValueProvider());
   7:     }       
   8:     private void InvokeAction(string actionName)
   9:     {
  10:         ControllerDescriptor cOntrollerDescriptor= new ReflectedControllerDescriptor(typeof(HomeController));
  11:         ReflectedActionDescriptor actiOnDescriptor= (ReflectedActionDescriptor)controllerDescriptor
  12:             .FindAction(ControllerContext, actionName);
  13:         actionDescriptor.MethodInfo.Invoke(this,this.ModelBinder.GetParameterValues(actionDescriptor).ToArray());
  14:     }
  15:     public void Index()
  16:     {
  17:         InvokeAction("Action");
  18:     }
  19:  
  20:     private IValueProvider GetValueProvider()
  21:     {
  22:         NameValueCollection requestData = new NameValueCollection();
  23:         requestData.Add("foo", "abc");
  24:         requestData.Add("bar", "123");
  25:         requestData.Add("baz", "123.45");
  26:         return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);
  27:     }
  28:     public void Action(string foo, [Bind(Prefix="baz")]double bar)
  29:     {
  30:         Response.Write(string.Format("{0}: {1}
"
, "foo", foo));
  31:         Response.Write(string.Format("{0}: {1}
"
, "bar", bar));
  32:     }
  33: }

InvokeAction方法用于执行指定的Action方法。在该方法中我们先根据当前Controller的类型创建一个ControllerDescriptor对象,并通过调其FindAction方法得到用于描述指定Action方法的ActionDescriptor对象。通过之前的介绍我们知道这是一个ReflectedActionDescriptor对象,所以我们将其转化成ReflectedActionDescriptor类型得到Action方法对应的MethodInfo对象。最后调用DefaultModelBinder的GetParameterValues方法得到目标Action方法所有的参数,将其传入MethodInfo的Invoke方法以反射的形式对指定的Action方法进行执行。

默认的Action方法Index中我们通过执行InvokeAction方法来执行定义在HomeController的Action方法。通过上面的代码片断可以看出,该方法的两个参数foo和bar均为简单类型(string和double),在参数bar上还应用了BindAttribute并指定了相应的前缀(“baz”)。在该Action方法中,我们将两个参数值呈现出来。

而在用于提供ValueProvider的GetValueProvider方法返回的是一个NameValueCollectionValueProvider对象。作为数据源的NameValueCollection对象包含三个名称为foo、bar和baz的数据(abc、123、123.45),我们可以将它们看成是Post的标单输入元素。

当我们运行该程序的时候会在浏览器中得到如下的输出结果。我们可以看到目标Action方法的两个参数值均通过我们自定义的DefaultModelBinder得到了有效的绑定。而实际上参数值的提供最终是通过ValueProvider实现的,它在默认的情况下会根据参数名称进行匹配(foo参数),如果参数应用BindAttribute并显式指定了前缀,则会按照这个前缀进行匹配(bar参数)。

   1: foo: abc
   2: bar: 123.45

二、复杂类型

对于简单类型的参数来说,由于支持与字符串类型之间的转换,相应ValueProvider可以直接从数据源中提取相应的数据并直接转换成参数类型。所以针对简单类型的Model绑定是一步到位的过程,但是针对复杂类型的Model绑定就没有这么简单了。复杂对象可以表示为一个树形层次化结构,其对象本身和属性代表相应的节点,叶子节点代表简单数据类型属性。而ValueProvider采用的数据源是一个扁平的数据结构,它通过基于属性名称前缀的Key实现与这个对象树中对应叶子节点的映射。

   1: public class Contact
   2: {
   3:     public string Name { get; set; }
   4:     public string PhoneNo { get; set; }
   5:     public string EmailAddress { get; set; }
   6:     public Address Address { get; set; }
   7: }
   8: public class Address
   9: {
  10:     public string Province { get; set; }
  11:     public string City { get; set; }
  12:     public string District { get; set; }
  13:     public string Street { get; set; }
  14: }

以上面定于得这个Contact类型为例,它具有三个简单类型的属性(Name、PhoneNo和EmailAddress)和复杂类型Address的属性;而Address属性具有四个简单类型的属性。一个Contact对象的数据结构可以通过如下图所示的树来表示,这个树种的所有叶子节点均为简单类型。如果我们需要通过一个ValueProvider来构建一个完整的Contact对象,它必须能够提供所有所有叶子节点的数值,而ValueProvider通过基于属性名称前缀的Key实现与对应的叶子节点的映射。

image

实际上当我们调用HtmlHelper的模板方法EditorFor/EditorForModel的时候就是按照这样的匹配方式对标单元素进行命名的。假设在将Contact作为Model类型的强类型View中,我们通过调用HtmlHelper的扩展方法EditorFor将Model对象的所有信息以编辑的模式呈现出来。

   1: @model Contact
   2: @Html.EditorFor(m => m.Name)
   3: @Html.EditorFor(m => m.PhoneNo)
   4: @Html.EditorFor(m => m.EmailAddress)
   5: @Html.EditorFor(m => m.Address.Province)
   6: @Html.EditorFor(m => m.Address.City)
   7: @Html.EditorFor(m => m.Address.District)
   8: @Html.EditorFor(m => m.Address.Street)

下面的代码片断代表了作为Model对象的Contact在最终呈现出来的View中代表的HTML,我们可以清楚地看到这些表单元素完全是根据属性名称和类型层次结构进行命名的。随便提一下,对于基于提交表单的Model绑定来说,作为匹配的是表单元素的name属性而非id属性,所以这里的命名指的是name属性而非id属性。

   1: <input id="Name" name="Name" type="text" ... />
   2: <input id="PhoneNo" name="PhoneNo" type="text" ... />
   3: <input id="EmailAddress" name="EmailAddress" type="text" ... />
   4: <input id="Address_Province" name="Address.Province" type="text" ... />
   5: <input id="Address_City" name="Address.City" type="text" ... />
   6: <input id="Address_District" name="Address.District" type="text" ... />
   7: <input id="Address_Street" name="Address.Street" type="text"... />

对于用于模拟默认Model绑定机制的自定义DefaultModelBinder来说,我们仅仅提供了针对简单类型的绑定,现在我们对其进行完善是之可以提供对复杂类型的Model绑定。如下面的代码片断所示,在BindModel方法中我们创建了一个基于参数类型的ModelMetadata对象,并根据其IsComplexType属性判断参数类型是否为复杂类型。

   1: public class DefaultModelBinder
   2: {
   3:     //其他成员
   4:     public object BindModel(Type parameterType, string prefix)
   5:     {
   6:         if (!this.ValueProvider.ContainsPrefix(prefix))
   7:         {
   8:             return null;
   9:         }
  10:         ModelMetadata modelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => null, parameterType);
  11:         if (!modelMetadata.IsComplexType)
  12:         {
  13:             return this.ValueProvider.GetValue(prefix).ConvertTo(parameterType);
  14:         }            
  15:         object model = CreateModel(parameterType);
  16:         foreach (PropertyDescriptor property in TypeDescriptor.GetProperties(parameterType))
  17:         {                
  18:             string key = string.IsNullOrEmpty(prefix) ? property.Name : prefix + "." + property.Name;
  19:             property.SetValue(model, BindModel(property.PropertyType, key));
  20:         }
  21:         return model;
  22:     }
  23:     private object CreateModel(Type modelType)
  24:     {
  25:         Type type = modelType;
  26:         if (modelType.IsGenericType)
  27:         {
  28:             Type genericTypeDefinition = modelType.GetGenericTypeDefinition();
  29:             if (genericTypeDefinition == typeof(IDictionary<,>))
  30:             {
  31:                 type = typeof(Dictionary<,>).MakeGenericType(modelType.GetGenericArguments());
  32:             }
  33:             else if (((genericTypeDefinition == typeof(IEnumerable<>)) || (genericTypeDefinition == typeof(ICollection<>))) ||(genericTypeDefinition == typeof(IList<>)))
  34:             {
  35:                 type = typeof(List<>).MakeGenericType(
  36:                 modelType.GetGenericArguments());
  37:             }
  38:         }
  39:         return Activator.CreateInstance(type);
  40:     }
  41: }

如果参数为复杂类型,则通过调用CreateModel方法以反射的方式创建Model对象。CreateModel方法会被用于后面我们会介绍的基于集合和字典的Model绑定,所以我们这里还针对泛型的IDictionary<,>、IEnumerable<>、ICollection<>和IList<>类型作了相应地处理。具体来说,如果参数类型为IDictionary<,>,则创建一个Dictionary<,>对象,而对后三者则创建一个List<>对象,具体的泛型参数根据参数类型获取。对于一般的类型,我们直接通过Activator的CreateInstance方法根据参数类型创建相应的Model对象。

通过CreateModel方法创建的是针对参数类型的“空”对象,我们需要通过Model绑定对它的相关属性进行初始化。在BindModel方法中,我们遍历参数类型的所有属性,并在现有前缀的基础上加上“.{属性名称}”(如果当前前缀为空,则直接采用属性名称)作为绑定对应属性的前缀递归地调用BindModel方法得到属性值。我们最终通过反射的方式将得到值对属性进行赋值。

现在我们采用我们完善后的DefaultModelBinder来进行针对复杂类型的Model绑定。如下面的代码片断所示,我们对HomeController的Action方法进行了相应的修改使之具有两个Contact类型的参数foo和bar。在Action方法中,我们将这两个参数代表的Contact对象的相关信息呈现出来。

   1: public class HomeController : Controller
   2: {
   3:     public DefaultModelBinder ModelBinder { get; private set; }
   4:     public HomeController()
   5:     {
   6:         this.ModelBinder = new DefaultModelBinder(GetValueProvider());
   7:     }
   8:  
   9:     private IValueProvider GetValueProvider()
  10:     {
  11:         NameValueCollection requestData = new NameValueCollection();
  12:         requestData.Add("Name", "张三");
  13:         requestData.Add("PhoneNo", "123456789");
  14:         requestData.Add("EmailAddress", "zhangsan@gmail.com");
  15:         requestData.Add("Address.Province", "江苏");
  16:         requestData.Add("Address.City", "苏州");
  17:         requestData.Add("Address.District", "工业园区");
  18:         requestData.Add("Address.Street", "星湖街328号");
  19:         return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);
  20:     }    
  21:  
  22:     public void Index()
  23:     {
  24:         InvokeAction("Action");
  25:     }
  26:  
  27:     public void Action(Contact foo, Contact bar)
  28:     {
  29:         Response.Write("Foo
"
);
  30:         Response.Write(string.Format("{0}: {1}
"
, "Name", foo.Name));
  31:         Response.Write(string.Format("{0}: {1}
"
, "PhoneNo", foo.PhoneNo));
  32:         Response.Write(string.Format("{0}: {1}
"
, "EmailAddress", foo.EmailAddress));
  33:         Response.Write(string.Format("{0}: {1} {2} {3} {4}

"
, "Address",
  34:            foo.Address.Province, foo.Address.City, foo.Address.District, 
  35:            foo.Address.Street));
  36:  
  37:         Response.Write("Bar
"
);
  38:         Response.Write(string.Format("{0}: {1}
"
, "Name", bar.Name));
  39:         Response.Write(string.Format("{0}: {1}
"
, "PhoneNo", bar.PhoneNo));
  40:         Response.Write(string.Format("{0}: {1}
"
, "EmailAddress", bar.EmailAddress));
  41:         Response.Write(string.Format("{0}: {1} {2} {3} {4}
"
, "Address",
  42:             bar.Address.Province, bar.Address.City, bar.Address.District, 
  43:             bar.Address.Street)); 
  44:     }
  45: }

通过GetValueProvider方法提供的依然是一个NameValueCollectionValueProvider对象,我们将一个Contact对象包含的信息包含在它对应的NameValueCollection对象中。对于添加到NameValueCollection中的针对Contact对象的某个属性的数据条目,我们按照上面介绍的匹配规则对其命名。运行我们的实例程序,我们会在浏览器中得到如下所示的输出结果,我们从中可以看到Action方法的两个参数foo和bar通过我们自定义的DefaultModelBinder进行了正确地绑定,并且它们具有相同的值。

   1: Foo
   2: Name: 张三
   3: PhoneNo: 123456789
   4: EmailAddress: zhangsan@gmail.com
   5: Address: 江苏 苏州 工业园区 星湖街328号
   6:  
   7: Bar
   8: Name: 张三
   9: PhoneNo: 123456789
  10: EmailAddress: zhangsan@gmail.com
  11: Address: 江苏 苏州 工业园区 星湖街328号

之所以同一个Action方法中两个相同类型的参数会绑定相同的数据,使缘于之前介绍的去除前缀的后备Model绑定机制。由于请求数据中并不包含针对某个参数的前缀,所以在针对参数名称作为前缀的Model绑定失败的情况下,后备Model绑定会前缀为空字符串的情况下再次进行。

   1: public class HomeController : Controller
   2: {
   3:     //其他成员
   4:     private IValueProvider GetValueProvider()
   5:     {
   6:         NameValueCollection requestData = new NameValueCollection();
   7:         requestData.Add("foo.Name", "Foo");
   8:         requestData.Add("foo.PhoneNo", "123456789");
   9:         requestData.Add("foo.EmailAddress", "Foo@gmail.com");
  10:  
  11:         requestData.Add("bar.Name", "Bar");
  12:         requestData.Add("bar.PhoneNo", "987654321");
  13:         requestData.Add("bar.EmailAddress", "Bar@gmail.com");
  14:         return new NameValueCollectionValueProvider(requestData, CultureInfo.InvariantCulture);
  15:     }
  16:  
  17:     public void Action(Contact foo, Contact bar)
  18:     {
  19:         Response.Write("Foo
"
);
  20:         Response.Write(string.Format("{0}: {1}
"
, "Name", foo.Name));
  21:         Response.Write(string.Format("{0}: {1}

"
, "PhoneNo", foo.PhoneNo));
  22:         Response.Write(string.Format("{0}: {1}
"
, "EmailAddress", foo.EmailAddress));
  23:  
  24:         Response.Write("Bar
"
);
  25:         Response.Write(string.Format("{0}: {1}
"
, "Name", bar.Name));
  26:         Response.Write(string.Format("{0}: {1}
"
, "PhoneNo", bar.PhoneNo));
  27:         Response.Write(string.Format("{0}: {1}
"
, "EmailAddress", bar.EmailAddress));
  28:     }
  29: }

在如上所示的代码中,我们为NameValueCollectionValueProvider设置了基于“foo”和“bar”的前缀的两套数据,目的在为Action方法的foo和bar参数提供不同的数据。运行我们的程序后会在浏览器上得到如下所示的输出结果,可以看出Action方法的两个参数被绑定了不同的值。

   1: Foo
   2: Name: Foo
   3: PhoneNo: 123456789
   4: EmailAddress: Foo@gmail.com
   5:  
   6: Bar
   7: Name: Bar
   8: PhoneNo: 987654321
   9: EmailAddress: Bar@gmail.com

 

通过实例模拟ASP.NET MVC的Model绑定的机制[上篇] 
通过实例模拟ASP.NET MVC的Model绑定的机制[下篇]

作者:Artech
出处:http://artech.cnblogs.com/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。
推荐阅读
  • 基于Net Core 3.0与Web API的前后端分离开发:Vue.js在前端的应用
    本文介绍了如何使用Net Core 3.0和Web API进行前后端分离开发,并重点探讨了Vue.js在前端的应用。后端采用MySQL数据库和EF Core框架进行数据操作,开发环境为Windows 10和Visual Studio 2019,MySQL服务器版本为8.0.16。文章详细描述了API项目的创建过程、启动步骤以及必要的插件安装,为开发者提供了一套完整的开发指南。 ... [详细]
  • Spring框架中枚举参数的正确使用方法与技巧
    本文详细阐述了在Spring Boot框架中正确使用枚举参数的方法与技巧,旨在帮助开发者更高效地掌握和应用枚举类型的数据传递,适合对Spring Boot感兴趣的读者深入学习。 ... [详细]
  • 如何在C#中配置组合框的背景颜色? ... [详细]
  • Spring框架的核心组件与架构解析 ... [详细]
  • C#编程中按钮控件的使用与优化 ... [详细]
  • Silverlight 实战指南:深入解析用户提交数据的验证与捕获机制
    本文深入探讨了Silverlight中用户提交数据的验证与捕获机制,详细分析了四种主要的验证方法:基本异常处理、DataAnnotation注解、IDataErrorInfo客户端同步验证以及自定义验证策略。通过实例解析,帮助开发者更好地理解和应用这些机制,提升应用程序的数据处理能力和用户体验。 ... [详细]
  • Unity3D 中 AsyncOperation 实现异步场景加载及进度显示优化技巧
    在Unity3D中,通过使用`AsyncOperation`可以实现高效的异步场景加载,并结合进度条显示来提升用户体验。本文详细介绍了如何利用`AsyncOperation`进行异步加载,并提供了优化技巧,包括进度条的动态更新和加载过程中的性能优化方法。此外,还探讨了如何处理加载过程中可能出现的异常情况,确保加载过程的稳定性和可靠性。 ... [详细]
  • 在List和Set集合中存储Object类型的数据元素 ... [详细]
  • 本文深入解析了WCF Binding模型中的绑定元素,详细介绍了信道、信道管理器、信道监听器和信道工厂的概念与作用。从对象创建的角度来看,信道管理器负责信道的生成。具体而言,客户端的信道通过信道工厂进行实例化,而服务端则通过信道监听器来接收请求。文章还探讨了这些组件之间的交互机制及其在WCF通信中的重要性。 ... [详细]
  • Python 伦理黑客技术:深入探讨后门攻击(第三部分)
    在《Python 伦理黑客技术:深入探讨后门攻击(第三部分)》中,作者详细分析了后门攻击中的Socket问题。由于TCP协议基于流,难以确定消息批次的结束点,这给后门攻击的实现带来了挑战。为了解决这一问题,文章提出了一系列有效的技术方案,包括使用特定的分隔符和长度前缀,以确保数据包的准确传输和解析。这些方法不仅提高了攻击的隐蔽性和可靠性,还为安全研究人员提供了宝贵的参考。 ... [详细]
  • 优化后的标题:深入探讨网关安全:将微服务升级为OAuth2资源服务器的最佳实践
    本文深入探讨了如何将微服务升级为OAuth2资源服务器,以订单服务为例,详细介绍了在POM文件中添加 `spring-cloud-starter-oauth2` 依赖,并配置Spring Security以实现对微服务的保护。通过这一过程,不仅增强了系统的安全性,还提高了资源访问的可控性和灵活性。文章还讨论了最佳实践,包括如何配置OAuth2客户端和资源服务器,以及如何处理常见的安全问题和错误。 ... [详细]
  • 在本文中,我们将详细介绍如何构建一个用于自动回复消息的XML类。当微信服务器接收到用户消息时,该类将生成相应的自动回复消息。以下是具体的代码实现:```phpclass We_Xml { // 代码内容}```通过这个类,开发者可以轻松地处理各种消息类型,并实现高效的自动回复功能。我们将深入探讨类的各个方法和属性,帮助读者更好地理解和应用这一技术。 ... [详细]
  • C++ 异步编程中获取线程执行结果的方法与技巧及其在前端开发中的应用探讨
    本文探讨了C++异步编程中获取线程执行结果的方法与技巧,并深入分析了这些技术在前端开发中的应用。通过对比不同的异步编程模型,本文详细介绍了如何高效地处理多线程任务,确保程序的稳定性和性能。同时,文章还结合实际案例,展示了这些方法在前端异步编程中的具体实现和优化策略。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
  • 计算机视觉领域介绍 | 自然语言驱动的跨模态行人重识别前沿技术综述(上篇)
    本文介绍了计算机视觉领域的最新进展,特别是自然语言驱动的跨模态行人重识别技术。上篇内容详细探讨了该领域的基础理论、关键技术及当前的研究热点,为读者提供了全面的概述。 ... [详细]
author-avatar
美多小涛_584
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有