简单分析一下 RIA Services 的数据绑定原理
简单分析一下 RIA Services 的数据绑定原理.
Neil Chen, 11/25/2009
==================================================================
利用 RIA Services 的项目模板创建了一个 solution,其中包含一个 Silverlight App 和一个 ASP.NET Web App.
名称分别是 BusinessApplication1 和 BusinessApplication1.Web
在 Silverlight 项目的代码里,
首先定义了一个 DomainDataSource 对象,
<riacontrols:DomainDataSource.DomainContext>
<ds:AdvDomainContext />
</riacontrols:DomainDataSource.DomainContext>
</riacontrols:DomainDataSource>
这个东西定义在 System.Windows.Ria.Controls.dll 中的 System.Windows.Controls 名称空间下。
下面是一个简单的 DataGrid 的例子,通过 ItemsSource 绑定到上述数据源对象。
可以看到,其绑定 Path 是 "Data".
另一个复杂一点的柱形图绑定例子:
<chartingToolkit:Chart.Series>
<chartingToolkit:BarSeries
Title="SickLeave Hours"
ItemsSource="{Binding ElementName=MyData, Path=Data}"
IndependentValueBinding="{Binding Title}"
DependentValueBinding="{Binding SickLeaveHours}"/>
</chartingToolkit:Chart.Series>
</chartingToolkit:Chart>
那么我们用 Reflector 看一看 DomainDataSource 中 Data 属性的实现是怎样的.
{
get
{
return (IEnumerable) base.GetValue(DataProperty);
}
private set
{
if (this.Data != value)
{
this.SetValueNoCallback(DataProperty, value);
}
}
}
这里可以看到表示可枚举数据的 IEnumerable 对象其实是存在一个依赖属性 DataProperty 里面. 然后我们跟踪 private set 方法是何时被调用的,
可以发现下列代码:
{
this._internalEntityCollection = new DomainDataSourceEntityCollection(this);
EntityCollectionView view = new EntityCollectionView(this._internalEntityCollection, delegate {
this.EndDeferRefresh();
});
this._internalEntityCollection.EntityCollectionView = view;
// 这里设定了 Data 属性.
this.Data = view;
//
}
再进一步可以看到这个方法是被 DomainDataSource 类的构造器调用的。
我们可以看到这里的 view 实际上是一个 EntityCollectionView 类的实例。而这个类的签名比较复杂,它继承了一堆接口:
IPagedCollectionView, IEditableCollectionView, INotifyPropertyChanged
可以从接口名称大致看到这个集合类具备可枚举,变动通知,分页,编辑等功能。
而该对象的数据源又是一个 DomainDataSourceEntityCollection 的实例。见下列语句:
this._internalEntityCollection = new DomainDataSourceEntityCollection(this);
另一个用到的类 EntityCollectionView 的一些关键代码如下:
IPagedCollectionView, IEditableCollectionView, INotifyPropertyChanged
{
public EntityCollectionView(DomainDataSourceEntityCollection source, Action deferRefreshDisposedCallback)
{
//
this._sourceCollection = source;
//
this.CopySourceToInternalList();
//
}
public IEnumerator GetEnumerator()
{
//
// Call InternalList here.
enumerator = this.InternalList.GetEnumerator();
//
}
private void CopySourceToInternalList()
{
this._internalList = new List<object>();
// 这里获取数据源的 enumerator.
IEnumerator enumerator = this.SourceCollection.GetEnumerator();
while (enumerator.MoveNext())
{
this._internalList.Add(enumerator.Current);
}
}
public IEnumerable SourceCollection
{
get
{
return this._sourceCollection;
}
}
private IList InternalList
{
get
{
return this._internalList;
}
}
}
而
INotifyPropertyChanged, INotifyCollectionChanged
{
public IEnumerator<Entity> GetEnumerator()
{
return base.items.GetEnumerator();
}
}
现在需要知道 base.items 如何初始化.
这个对象是通过 this._internalEntityCollection = new DomainDataSourceEntityCollection(this);
初始化的。其中 this 是 DomainDataSource 对象.
于是追踪下列构造器
{
//
this._domainDataSource = domainDataSource;
}
但其中没有涉及 base.items 何时赋值的语句。可以断定,这里数据是后来加载的。
加载流程:
DomainDataSource 中设定了 DomainContext 属性,<riacontrols:DomainDataSource.DomainContext>
导致 DomainContextPropertyChanged 被调用。
{
DomainDataSource source = (DomainDataSource) depObj;
if ((source != null) && !source.IsHandlerSuspended(e.Property))
{
// 得到 QueryName 对应的 MethodInfo 等信息
MethodInfo info;
PropertyInfo info2;
Type type;
if (e.OldValue != null)
{
source.SetValueNoCallback(e.Property, e.OldValue);
throw new InvalidOperationException(DomainDataSourceResources.DomainContextAlreadySet);
}
DomainContext newValue = e.NewValue as DomainContext;
Type domainContextType = newValue.GetType();
// 关键点。。
Exception exception = CheckEntityQueryInformation(GetEntityQueryInformation(domainContextType, source.QueryName, source.QueryParameters, out info, out info2, out type), domainContextType, source.QueryName, type);
if (exception != null)
{
source.SetValueNoCallback(e.Property, e.OldValue);
throw exception;
}
source._entityType = type;
source._queryMethod = info;
newValue.PropertyChanged += new PropertyChangedEventHandler(source.DomainContext_PropertyChanged);
source.GetEntityList(info2);
source.HasChanges = newValue.HasChanges;
// 调用 CheckAutoLoad 方法来触发获取数据的操作.
exception = source.CheckAutoLoad(true, false);
if (exception != null)
{
throw exception;
}
}
}
下面是获取 DomainContext 对象上定义的元数据的实现:
{
Func<KeyValuePair<MethodInfo, Type>, bool> predicate = null;
entityQueryMethodInfo = null;
domainContextEntityListPropertyInfo = null;
entityType = null;
if ((domainContextType == null) || string.IsNullOrEmpty(queryName))
{
return MethodAccessStatus.InsufficientInput;
}
string suffixedQueryName = queryName + "Query";
IEnumerable<KeyValuePair<MethodInfo, Type>> source = domainContextType.GetMethods(BindingFlags.Public | BindingFlags.Instance).Where<MethodInfo>(delegate (MethodInfo method) {
if (!string.Equals(method.Name, suffixedQueryName, StringComparison.Ordinal))
{
return string.Equals(method.Name, queryName, StringComparison.Ordinal);
}
return true;
}).Select(delegate (MethodInfo method) {
return new { method = method, entityQueryEntityType = GetEntityQueryEntityType(method.ReturnType) };
}).Where(delegate (<>f__AnonymousType1<MethodInfo, Type> <>h__TransparentIdentifier0) {
return (<>h__TransparentIdentifier0.entityQueryEntityType != null);
}).Select(delegate (<>f__AnonymousType1<MethodInfo, Type> <>h__TransparentIdentifier0) {
return new KeyValuePair<MethodInfo, Type>(<>h__TransparentIdentifier0.method, <>h__TransparentIdentifier0.entityQueryEntityType);
});
if (!source.Any<KeyValuePair<MethodInfo, Type>>())
{
return MethodAccessStatus.NameNotFound;
}
KeyValuePair<MethodInfo, Type>[] pairArray = source.Where<KeyValuePair<MethodInfo, Type>>(delegate (KeyValuePair<MethodInfo, Type> methodType) {
return MethodParametersMatchQueryParameters(methodType.Key, queryParameters);
}).ToArray<KeyValuePair<MethodInfo, Type>>();
if (pairArray.Length > 1)
{
if (predicate == null)
{
predicate = delegate (KeyValuePair<MethodInfo, Type> o) {
return o.Key.Name.Equals(suffixedQueryName, StringComparison.Ordinal);
};
}
pairArray = pairArray.Where<KeyValuePair<MethodInfo, Type>>(predicate).ToArray<KeyValuePair<MethodInfo, Type>>();
}
if (pairArray.Length != 1)
{
return MethodAccessStatus.ArgumentMismatch;
}
KeyValuePair<MethodInfo, Type> pair = pairArray[0];
Type entityListType = typeof(EntityList<>).MakeGenericType(new Type[] { pair.Value });
IEnumerable<PropertyInfo> enumerable2 = domainContextType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where<PropertyInfo>(delegate (PropertyInfo property) {
return property.PropertyType == entityListType;
});
entityType = pair.Value;
int num = enumerable2.Count<PropertyInfo>();
if (num == 0)
{
return MethodAccessStatus.EntityListNotFound;
}
if (num > 1)
{
return MethodAccessStatus.AmbiguousEntityList;
}
entityQueryMethodInfo = pair.Key;
domainContextEntityListPropertyInfo = enumerable2.Single<PropertyInfo>();
return MethodAccessStatus.Success;
}
追踪调用堆栈:
DomainDataSource.ExecuteLoad()
DomainDataSource.LoadData()
DomainDataSource.LoadData_Callback()
DomainDataSource.DomainContext_Loaded()
DomainDataSourceEntityCollection.AddLoadedEntity() // data items are added to internal base.items collection.
{
//
callback = delegate (LoadOperation loadOperation) {
this.LoadData_Callback(loadOperation);
};
//
}
private void LoadData_Callback(LoadOperation loadOperation)
{
LoadedDataEventArgs e = new LoadedDataEventArgs(loadOperation.Entities, loadOperation.AllEntities, loadOperation.TotalEntityCount, loadOperation.Error, loadOperation.IsCanceled, loadOperation.UserState);
this.DomainContext_Loaded(this, e);
//
}
private void DomainContext_Loaded(object sender, LoadedDataEventArgs e)
{
//
foreach (Entity entity in e.Entities.Where<Entity>(delegate (Entity entity) {
return entity.EntityState != EntityState.Deleted;
}))
{
this._internalEntityCollection.AddLoadedEntity(entity);
}
//
}
internal void AddLoadedEntity(Entity loadedEntity)
{
//
base.Add(loadedEntity);
}
到目前为止,可以看到全部的加载流程如下:
============================================
DomainDataSource 中设定了 DomainContext 属性,<riacontrols:DomainDataSource.DomainContext>
导致 DomainContextPropertyChanged 被调用。
DomainDataSource.CheckAutoLoad()
DomainDataSource.ExecuteLoad()
DomainDataSource.LoadData()
DomainDataSource.LoadData_Callback()
DomainDataSource.DomainContext_Loaded()
DomainDataSourceEntityCollection.AddLoadedEntity() // data items are added to internal base.items collection.
这时如果控件通过 Path=Data 绑定到 DomainDataSource 的 Data 属性,
对这个控件的 GetEnumerator() 操作会转发到 EntityCollectionView 类的一个实例,
EntityCollectionView 的构造函数中,会 copy 一个 DomainDataSourceEntityCollection 对象的数据到内部的数据列表。
而该 DomainDataSourceEntityCollection 中的数据,是在前面 DomainContext 属性变动时,由 AutoLoad 属性引发自动填充的。
现在来仔细看一下 DomainContext 对象是如何获取数据的。
在这段代码中:
<riacontrols:DomainDataSource.DomainContext>
<ds:AdvDomainContext />
</riacontrols:DomainDataSource.DomainContext>
</riacontrols:DomainDataSource>
<ds:AdvDomainContext /> 是一个 DomainContext 对象,而名称空间 ds 的定义如下:
xmlns:ds="clr-namespace:BusinessApplication1.Web.Services"
我们看看 BusinessApplication1\Generated_Code\ 下的 BusinessApplication1.Web.g.cs,
这个自动生成的代码里有一个类,
正是 <ds:AdvDomainContext />.
其默认构造器如下:
this(new HttpDomainClient(new Uri("DataService.axd/BusinessApplication1-Web-Services-AdvDomainService/", System.UriKind.Relative)))
{
}
注意到这里指向了一个服务的 Uri. "DataService.axd".
那我们到 \BusinessApplication1.Web 下面看一下 web.config 里面搜一下,发现有这么一段:
<add path="DataService.axd" verb="GET,POST"
type="System.Web.Ria.DataServiceFactory, System.Web.Ria, Version=2.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>
</httpHandlers>
对了,这个就是动态注册的一个 http handler. 其定义可以去看 System.Web.Ria.dll 中 System.Web.Ria.DataServiceFactory 类的实现。
可以看到, AdvDomainContext 的代码非常简单,比如像下面的两个方法,
{
get
{
return base.Entities.GetEntityList<Employee>();
}
}
/// <summary>
/// Returns an EntityQuery for query operation 'GetEmployee'.
/// </summary>
public EntityQuery<Employee> GetEmployeeQuery()
{
return base.CreateQuery<Employee>("GetEmployee", null, false, true);
}
主要是利用了 DomainContext 基类里面的一些方法。晚一点我们再来研究详细的内容,看来值得关注的至少有
base.Entities.GetEntityList<T>()
和
base.CreateQuery<T>(..)
这两个方法的实现。
回过头去看一下 System.Web.Ria.dll 里面 DataServiceFactory 的实现。
可以看到,
GetHandler() 方法调用了 GetDataService(),
而后者,根据 context.Request.PathInfo,也就是请求中的字符串信息,
按一定的规则拆分后,创建了符合要求的 DataService 类以处理请求。
回忆前面,路径的字符串其实就是这个东西:
DataService.axd/BusinessApplication1-Web-Services-AdvDomainService/
其中包含了要动态创建的类型的名字等信息.
这里 BusinessApplication1-Web-Services-AdvDomainService
首先将其中 - 替换为 .,变成
BusinessApplication1.Web.Services.AdvDomainService
而这个类就是 VS 项目模板在服务端自动生成的一个类,在 BusinessApplication1.Web\Services 下的
AdvDomainService.cs 文件中。
接下来,会创建一个 System.Web.Ria.DataService 对象,并利用以上 AdvDomainService 实例的信息将其初始化然后返回。
internal sealed class DataService : IHttpHandler, IServiceProvider, IDisposable
这里就进入了一个标准的 WCF Service 的处理流程。。。
值得一提的是,处理具体请求用的是 System.Web.Ria.DataServiceSubmitRequest 类的下列方法:
public override object Invoke(DomainService domainService)
然后,调用到 System.Web.DomainServices.DomainService 的
public virtual void Submit(ChangeSet changeSet) 方法.