一句代码实现批量“.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;
this .Control = control;
this .ControlValueProperty = controlValueProperty;
this 上海企业网站制作="color: #000000;">.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 上海徐汇企业网站设计与制作le="color: #0000ff;">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 , 上海闵行企业网站设计与制作n style="color: #0000ff;">new DataBindingEventArgs(bindingMapping, value));
}
}
}

  DataBinder设计的目标是让默认的绑定行为解决80%的问题,并且提供给相应的方式去解决余下的问题。为了让开发者能够有效解决余下的这20%的绑定问题,我们定义两个事件:DataItemB上海徐汇企业网站制作inding和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呼唤一下而已,所以在这里我就不再赘言叙述了。

目录
相关文章
|
4月前
|
API
【Azure 媒体服务】Media Service的编码示例 -- 创建缩略图子画面的.NET代码调试问题
【Azure 媒体服务】Media Service的编码示例 -- 创建缩略图子画面的.NET代码调试问题
|
24天前
|
开发框架 .NET PHP
ASP.NET Web Pages - 添加 Razor 代码
ASP.NET Web Pages 使用 Razor 标记添加服务器端代码,支持 C# 和 Visual Basic。Razor 语法简洁易学,类似于 ASP 和 PHP。例如,在网页中加入 `@DateTime.Now` 可以实时显示当前时间。
|
2月前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
Android远程连接和登录FTPS服务代码(commons.net库)
33 1
|
2月前
|
前端开发 JavaScript C#
CodeMaid:一款基于.NET开发的Visual Studio代码简化和整理实用插件
CodeMaid:一款基于.NET开发的Visual Studio代码简化和整理实用插件
|
4月前
|
Kubernetes 监控 Devops
【独家揭秘】.NET项目中的DevOps实践:从代码提交到生产部署,你不知道的那些事!
【8月更文挑战第28天】.NET 项目中的 DevOps 实践贯穿代码提交到生产部署全流程,涵盖健壮的源代码管理、GitFlow 工作流、持续集成与部署、容器化及监控日志记录。通过 Git、CI/CD 工具、Kubernetes 及日志框架的最佳实践应用,显著提升软件开发效率与质量。本文通过具体示例,助力开发者构建高效可靠的 DevOps 流程,确保项目成功交付。
95 0
|
4月前
|
API
【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret)
【Azure Key Vault】.NET 代码如何访问中国区的Key Vault中的机密信息(Get/Set Secret)
|
4月前
|
安全 Java 网络安全
Android远程连接和登录FTPS服务代码(commons.net库)
很多文章都介绍了FTPClient如何连接ftp服务器,但却很少有人说如何连接一台开了SSL认证的ftp服务器,现在代码来了。
118 2
|
4月前
|
微服务 API Java
微服务架构大揭秘!Play Framework如何助力构建松耦合系统?一场技术革命即将上演!
【8月更文挑战第31天】互联网技术飞速发展,微服务架构成为企业级应用主流。微服务将单一应用拆分成多个小服务,通过轻量级通信机制交互。高性能Java Web框架Play Framework具备轻量级、易扩展特性,适合构建微服务。本文探讨使用Play Framework构建松耦合微服务系统的方法。Play采用响应式编程模型,支持模块化开发,提供丰富生态系统,便于快速构建功能完善的微服务。
55 0
|
4月前
|
SQL 开发框架 .NET
代码更简洁,开发更高效:从零开始使用Entity Framework Core与传统ADO.NET构建数据持久化层的比较
【8月更文挑战第31天】在.NET平台上开发数据驱动应用时,选择合适的ORM框架至关重要。本文通过对比传统的ADO.NET和现代的Entity Framework Core (EF Core),展示了如何从零开始构建数据持久化层。ADO.NET虽强大灵活,但需要大量手写代码;EF Core则简化了数据访问,支持LINQ查询,自动生成SQL命令,提升开发效率。从创建.NET Core项目、定义数据模型、配置`DbContext`到执行数据库操作,EF Core提供了一套流畅的API,使数据持久化层的构建变得简单直接。
58 0
|
4月前
|
存储 Linux 网络安全
【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Linux/Linux Container)
【Azure App Service】.NET代码实验App Service应用中获取TLS/SSL 证书 (App Service Linux/Linux Container)