一句代码实现批量数据绑定“.NET技术”[下篇]

简介:   《上篇》主要介绍如何通过DataBinder实现批量的数据绑定,以及如何解决常见的数据绑定问题,比如数据的格式化。接下来,我们主要来谈谈DataBinder的设计,看看它是如何做到将作为数据源实体的属性值绑定到界面对应的控件上的。

  《上篇》主要介绍如何通过DataBinder实现批量的数据绑定,以及如何解决常见的数据绑定问题,比如数据的格式化。接下来,我们主要来谈谈DataBinder的设计,看看它是如何做到将作为数据源实体的属性值绑定到界面对应的控件上的。此外,需要特别说明一点:《上篇》中提供了DataBinder最初版本的下载,但已经和本篇文章介绍的已经大不一样了。最新版本的主要解决两个主要问题:通过Expression Tree的方式进行属性操作(属性赋值和取值),添加了“数据捕捉”(Data Capture)的功能,以实现将控件中的值赋给指定的实体。但是,这并不意味着这就是一个最终版本,这里面依然有一些问题,比如对空值的处理不不够全面,比如在进行数据绑定的时候,有的控件类型需要进行HTML Encoding,等等。[源代码从这里下载]

上海企业网站制作

目录:
一、通过DataPropertyAttribute特性过滤实体的“数据属性”
二、Control/DataSource映射的表示:BindingMapping
三、如何建立Control/DataSource映射集合
四、通过映射集合实现数据绑定
五、通过映射集合实现数据捕捉

  一、通过DataPropertyAttribute特性过滤实体的数据属性

  DataBinder在进行数据绑定的时候,并没有对作为数据源的对象作任何限制,也就是说任何类型的对象均可作为数据绑定的数据源。控件(这里指TextBox、Label等这样绑定标量数值的控件)绑定值来源于数据源实体的某个属性。但是一个类型的属性可能有很多,我们需要某种筛选机制将我们需要的“数据属性”提取出来。这里我们是通过在属性上应用DataPropertyAttribute一个特性来实现的。

  简单起见,我不曾为DataPropertyAttribute定义任何属性成员。DataPropertyAttribute中定义了一个静态的GetDataProperties方法,得到给定实体类型的所有数据属性的名称。但是为了避免频繁地对相同实体类型进行反射,该方法对得到的属性名称数组进行了缓存。

 
 
[AttributeUsage( AttributeTargets.Property, AllowMultiple = false ,Inherited = true )]
public class DataPropertyAttribute: Attribute
{
private static Dictionary < Type, string [] > dataProperties = new Dictionary < Type, string [] > ();
public static string [] GetDataProperties(Type entityType)
{
Guard.ArgumentNotNullOrEmpty(entityType,
" entityType " );
if (dataProperties.ContainsKey(entityType))
{
return dataProperties[entityType];
}
lock ( typeof (DataPropertyAttribute))
{
if (dataProperties.ContainsKey(entityType))
{
return dataProperties[entityType];
}
var properties
= (from property in entityType.GetProperties()
where property.GetCustomAttributes( typeof (DataPropertyAttribute), true ).Any()
select property.Name).ToArray();
dataProperties[entityType]
= properties;
return properties;
}
}
}

  二、Control/DataSource映射的表示:BindingMapping

  不论是数据绑定(实体=〉控件),还是数据捕捉(控件=〉实体)的实现都建立在两种之间存在着某种约定的映射之上,这个映射是整个DataBinder的核心所在。在这里,我定义了如下一个BindingMapping类型表示这个映射关系。

 
 
public class BindingMapping: ICloneable
{
public Type DataSourceType { get ; private set ; }
public Control Control { get ; set ; }
public string ControlValueProperty { get ; set ; }
public string DataSourceProperty { get ; set ; }
public bool AutomaticBind { get ; set ; }
public bool AutomaticUpdate { get ; set ; }
public string FormatString { get ; set ; }
public Type ControlValuePropertyType
{
get { return PropertyAccessor.GetPropertyType( this .Control.GetType(), this .ControlValueProperty); }
}
public Type DataSourcePropertyType
{
get { return PropertyAccessor.GetPropertyType( this .DataSourceType, this .DataSourceProperty); }
}

public BindingMapping(Type dataSourceType, Control control, string controlValueProperty, string dataSourceProperty)
{
// ...
this .DataSourceType = dataSourceType;
thi上海网站建设s .Control = control;
this .ControlValueProperty = controlValueProperty;
this .DataSourceProperty = dataSourceProperty;
this .AutomaticBind = true ;
this .AutomaticUpdate = true ;
}
object ICloneable.Clone()
{
return this .Clone();
}
public BindingMapping Clone()
{
var bindingMapping
= new BindingMapping( this .DataSourceType, this .Control, this .ControlValueProperty, this .DataSourceProperty);
bindingMapping.AutomaticBind
= this .AutomaticBind;
bindingMapping.AutomaticUpdate
= this .AutomaticBind;
return bindingMapping;
}
}

  这里我主要介绍一下各个属性的含义:

  • DataSourceType:作为数据源实体的类型;
  • Control:需要绑定的控件;
  • ControlValueProperty:数据需要绑定到控件属性的名称,比如TextBox是Text属性,而RadioButtonList则是SelectedValue属性;
  • DataSourceProperty:实体类型中的数据属性名称
  • AutomaticBind:是否需要进行自动绑定,通过它阻止不必要的自动数据绑定行为。默认值为True,如果改成False,基于该条映射的绑定将被忽略;
  • AutomaticUpdate:是否需要进行自动更新到数据实体中,通过它阻止不必要的自动数据捕捉行为。默认值为True,如果改成False,基于该条映射的数据捕捉定将被忽略;
  • FormatString:格式化字符串;
  • ControlValuePropertyType:控件绑定属性的类型,比如TextBox的绑定属性为Text,那么ControlValuePropertyType为System.String;
  • DataSourcePropertyType:实体属性类型。

  需要补充一点的是:ControlValuePropertyType和DataSourcePropertyType使用到了之前定义的用于操作操作属性的组件ProcessAccessor。BindingMapping采用了克隆模式。

  三、如何建立Control/DataSource映射集合

  BindingMapping表示的一个实体类型的数据属性和具体控件之间的映射关系,而这种关系在使用过程中是以批量的方式进行创建的。具体来说,我们通过指定实体类型和一个作为容器的空间,如果容器中的存在满足映射规则的子控件,相应的映射会被创建。映射的批量创建是通过DataBinder的静态方法BuildBindingMappings来实现的。

  在具体介绍BuildBindingMappings方法之前,我们需要先来讨论一个相关的话题:在进行数据绑定的时候,如何决定数据应该赋值给控件的那个属性。我们知道,不同的控件类型拥有不同的数据绑定属性,比如TextBox自然是Text属性,CheckBox则是Checked属性。ASP.NET在定义控件类型的时候,采用了一个特殊性的特性ControlValuePropertyAttribute来表示那个属性表示的是控件的“值”。比如TextBox和CheckBox分别是这样定义的。

 
 
[ControlValueProperty( " Text " )]
public class TextBox : WebControl, IPostBackDataHandler, IEditableTextControl, ITextControl
{
// ...
}

ControlValueProperty(
" Checked " )]
public class CheckBox : WebControl, IPostBackDataHandler, ICheckBoxControl
{
// ...
}

  在这里我们直接将ControlValuePropertyAttribute中指定的名称作为控件绑定的属性名,即BindingMapping的ControlValueProperty属性。该值得获取通过如下一个GetControlValuePropertyName私有方法完成。为了避免重复反射操作,这里采用了全局缓存。

 
 
private static string GetControlValuePropertyName(Control control)
{
if ( null == control)
{
return null ;
}
Type entityType
= control.GetType();
if (controlValueProperties.ContainsKey(entityType))
{
return controlValueProperties[entityType];
}
lock ( typeof (DataBinder))
{
if (controlValueProperties.ContainsKey(entityType))
{
return controlValueProperties[entityType];
}
ControlValuePropertyAttribute controlValuePropertyAttribute
= (ControlValuePropertyAttribute)entityType.GetCustomAttributes( typeof (ControlValuePropertyAttribute), true )[ 0 ];
controlValueProperties[entityType]
= controlValuePropertyAttribute.Name;
return controlValuePropertyAttribute.Name;
}
}

  最终的映射通过如下定义的BuildBindingMappings方法来建立,缺省参数suffix代表的是控件的后缀,其中已经在《上篇》介绍过了。

 
 
public static IEnumerable < BindingMapping > BuildBindingMappings(Type entityType, Control container, string suffix = "" )
{
// ...
suffix = suffix ?? string .Empty;
return (from property in DataPropertyAttribute.GetDataProperties(entityType)
let control
= container.FindControl( string .Format( " {1}{0} " , suffix, property))
let controlValueProperty
= GetControlValuePropertyName(control)
where null != control
select
new BindingMapping(entityType, control, controlValueProperty, property)).ToArray();
}

  四、通过映射集合实现数据绑定

  通过《上篇》我们知道,DataBinder提供两种数据绑定方式:一种是直接通过传入数据实体对象和容器控件对具有匹配关系的所有子控件进行绑定;另外一种则是通过调用上面BuildBindingMappings静态方法建立的BindingMapping集合,然后再借助于这个集合进行数据绑定。这两种方式的数据绑定对应于如下两个重载的BindData方法:

 
 
public class DataBinder
{
// ...
public void BindData( object entity, Control container, string suffix = "" );
public void BindData( object entity,IEnumerable < BindingMapping > bindingMappings);
}

  已经上在内部,上面一个方法也是需要通过调用BuildBindingMappings来建立映射。数据绑定始终是根据BindingMapping集合进行的。由于在BindingMapping中已经定义了完成数据绑定所需的必要信息,数据绑定的逻辑变得很简单。具体来说,数据绑定的逻辑是这样的:遍历所有的集合中每个BindingMapping,根据DataSourceProperty得到属性名称,然后进一步从数据源实体中得到具体的值。根据ControlValuePropertyType得到目标控件绑定属性的类型,然后将之前得到的值转换成该类型。最后,通过ControlValueProperty得到控件的绑定属性,将之前经过转换的值给控件的这个属性就可以了。整个数据绑定实现在如下一个OnBindData方法中。关于属性操作则借助于PropertyAccessor这个组件。

 
 
protected virtual void OnBindData(IEnumerable < BindingMapping > bindingMappings, object entity)
{
foreach (var mapping in bindingMappings)
{
var bindingMapping
= mapping.Clone();
object value = PropertyAccessor.Get(entity, bindingMapping.DataSourceProperty);
if ( null != this .DataItemBinding)
{
var args
= new DataBindingEventArgs(bindingMapping, value);
this .DataItemBinding( this , args);
value
= args.DataValue;
}
if ( ! bindingMapping.AutomaticBind)
{
continue ;
}

if ( ! string .IsNullOrEmpty(bindingMapping.FormatString))
{
value
= Format(value, bindingMapping.FormatString);
}

Type controlValuePropertyType
= PropertyAccessor.GetPropertyType(bindingMapping.Control.GetType(), bindingMapping.ControlValueProperty);
value
= ChangeType(value, controlValuePropertyType);
if ( null == value && typeof (ValueType).IsAssignableFrom(controlValuePropertyType))
{
value
= Activator.CreateInstance(controlValuePropertyType);
}
PropertyAccessor.Set(bindingMapping.Control, bindingMapping.ControlValueProperty, value);
if ( null != this .DataItemBound)
{
this .DataItemBound( this , new DataBindingEventArgs(bindingMapping, value));
}
}
}

  DataBinder设计的目标是让默认的绑定行为解决80%的问题,并且提供给相应的方式去解决余下的问题。为了让开发者能够有效解决余下的这20%的绑定问题,我们定义两个事件:DataItemBinding和DataBound,它们分别在进行绑定之前和之后被触发。关于事件的触发,已经体现在OnBindData方法的定义中了。

  五、通过映射集合实现数据捕捉

  数据绑定使用到的实际上是Entity-〉Control映射,如果我们借助控件到Control-〉Entity,就能实现自动捕获控件的值然后将其保存到给定的实体对象上。我为此在DataBinder上定义了两个重载的UpdateData方法。

 
 
public class DataBinder
{
// ...
public void BindData( object entity,IEnumerable < BindingMapping > bindingMappings);
public void UpdateData( object entity, Control container, string suffix = "" );
}

  UpdateData方法的实现和BindData方法的逻辑基本一致,将Control和Entity呼唤一下而已,所以在这里我就不再赘言叙述了。

目录
相关文章
|
8月前
|
监控 Cloud Native 测试技术
.NET技术深度解析:现代企业级开发指南
每日激励:“不要一直责怪过去的自己,他曾经站在雾里也很迷茫”。我是蒋星熠Jaxonic,一名在代码宇宙中探索的极客旅人。从.NET Framework到.NET 8,我深耕跨平台、高性能、云原生开发,践行领域驱动设计与微服务架构,用代码书写技术诗篇。分享架构演进、性能优化与AI融合前沿,助力开发者在二进制星河中逐光前行。关注我,共探技术无限可能!
.NET技术深度解析:现代企业级开发指南
|
SQL 小程序 API
如何运用C#.NET技术快速开发一套掌上医院系统?
本方案基于C#.NET技术快速构建掌上医院系统,结合模块化开发理念与医院信息化需求。核心功能涵盖用户端的预约挂号、在线问诊、报告查询等,以及管理端的排班管理和数据统计。采用.NET Core Web API与uni-app实现前后端分离,支持跨平台小程序开发。数据库选用SQL Server 2012,并通过读写分离与索引优化提升性能。部署方案包括Windows Server与负载均衡设计,确保高可用性。同时针对API差异、数据库老化及高并发等问题制定应对措施,保障系统稳定运行。推荐使用Postman、Redgate等工具辅助开发,提升效率与质量。
608 0
|
算法 Java 测试技术
使用 BenchmarkDotNet 对 .NET 代码进行性能基准测试
使用 BenchmarkDotNet 对 .NET 代码进行性能基准测试
458 13
|
开发框架 算法 .NET
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
C#/.NET/.NET Core技术前沿周刊 | 第 15 期(2024年11.25-11.30)
310 6
|
开发框架 Cloud Native .NET
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
C#/.NET/.NET Core技术前沿周刊 | 第 16 期(2024年12.01-12.08)
351 6
|
自然语言处理 物联网 图形学
.NET 技术凭借其独特的优势和特性,为开发者们提供了一种高效、可靠且富有创造力的开发体验
本文深入探讨了.NET技术的独特优势及其在多个领域的应用,包括企业级应用、Web应用、桌面应用、移动应用和游戏开发。通过强大的工具集、高效的代码管理、跨平台支持及稳定的性能,.NET为开发者提供了高效、可靠的开发体验,并面对技术更新和竞争压力,不断创新发展。
773 7
|
开发框架 .NET PHP
ASP.NET Web Pages - 添加 Razor 代码
ASP.NET Web Pages 使用 Razor 标记添加服务器端代码,支持 C# 和 Visual Basic。Razor 语法简洁易学,类似于 ASP 和 PHP。例如,在网页中加入 `@DateTime.Now` 可以实时显示当前时间。
|
开发框架 安全 .NET
在数字化时代,.NET 技术凭借跨平台兼容性、丰富的开发工具和框架、高效的性能及强大的安全稳定性,成为软件开发的重要支柱
在数字化时代,.NET 技术凭借跨平台兼容性、丰富的开发工具和框架、高效的性能及强大的安全稳定性,成为软件开发的重要支柱。它不仅加速了应用开发进程,提升了开发质量和可靠性,还促进了创新和业务发展,培养了专业人才和技术社区,为软件开发和数字化转型做出了重要贡献。
425 5
|
机器学习/深度学习 人工智能 物联网
.NET 技术:引领未来开发潮流
.NET 技术以其跨平台兼容性、高效的开发体验、强大的性能表现和安全可靠的架构,成为引领未来开发潮流的重要力量。本文深入探讨了 .NET 的核心优势与特点,及其在企业级应用、移动开发、云计算、人工智能等领域的广泛应用,展示了其卓越的应用价值和未来发展前景。
321 5
|
传感器 人工智能 供应链
.NET开发技术在数字化时代的创新作用,从高效的开发环境、强大的性能表现、丰富的库和框架资源等方面揭示了其关键优势。
本文深入探讨了.NET开发技术在数字化时代的创新作用,从高效的开发环境、强大的性能表现、丰富的库和框架资源等方面揭示了其关键优势。通过企业级应用、Web应用及移动应用的创新案例,展示了.NET在各领域的广泛应用和巨大潜力。展望未来,.NET将与新兴技术深度融合,拓展跨平台开发,推动云原生应用发展,持续创新。
250 4