21.1 数据服务
21.1.1基本操作功能
Phenixヾ的数据服务,提供了如下的基本操作:
功能 |
Type |
URI |
参数 |
完整获取实体集合对象 |
GET |
api/Data |
|
分页获取实体集合对象 |
GET |
api/Data |
pageSize=[分页大小]&pageNo=[分页号] |
按条件获取实体集合对象 |
GET |
api/Data |
id=[条件对象] |
按条件分页获取实体集合对象 |
GET |
api/Data |
id=[条件对象]&pageSize=[分页大小]&pageNo=[分页号] |
按主对象获取实体集合对象 |
GET |
api/Data |
masterId=[主对象键值]&groupName=[分组名] |
按主对象分页获取实体集合对象 |
GET |
api/Data |
masterId=[主对象键值]&groupName=[分组名]&pageSize=[分页大小]&pageNo=[分页号] |
获取单个实体对象 |
GET |
api/Data |
id=[主键值] |
提交数据 |
POST |
api/Data |
|
执行服务 |
POST |
api/Data |
注:可操作的实体范围,除受到普通的权限管理控制外,仍可以受到“切片管理”(参见:《Phenix.NET企业应用软件快速开发平台.权限管理.01.简介》)的控制。
从上述接口可见,Phenixヾ的WebAPI服务提供的是通用的接口(仅实现了一个API Controller),即DataController。
这样,为了区分不同的资源,要求HTTP客户端将资源信息藏在HTTP请求的标头Header里:
Header Name |
内容 |
适用于 |
说明 |
Phenix-Data-Name |
数据名/服务名 |
GET、POST |
在服务端注册的实体集合类全名、实体类全名、服务类全名(需实现IEntityCollection、IEntity、IService接口) |
Phenix-Criteria-Name |
条件名 |
GET |
在服务端定义的条件类全名(需实现ICriteria接口) |
Phenix-Master-Name |
主对象名 |
GET |
在服务端注册的实体类全名(需实现IEntity接口) |
注: 实体对象的操作请求,在Http请求标头上标识“Phenix-Data-Name”内容是必须的。否则,如果没有标识上,且是未带参数的Get请求的话,服务端返回的则是Sequence(64位序列号)。
不管我们采用是什么技术、什么平台,HTTP客户端的开发,只要符合上述HTTP消息的规范要求,就能得到Phenixヾ的WebAPI数据服务。
21.1.2数据服务代理
为了说明HTTP客户端如何调用WebAPI数据服务,Phenixヾ在“Phenix.Web.Client”工程里提供了一套标准的数据服务代理类DataProxy。我们可以依照这个实例里的代码,作为样例来编写出适用于自己技术平台的HTTP客户端:
21.1.2.1 Fetch
21.1.2.1.1 方法
检索数据,分检索单个实体和实体集合,都使用到了Get操作:
/// <summary>
/// 获取实体
/// </summary>
/// <param name="dataName">数据名, 在服务端注册的实体集合类全名(需实现IEntityCollection接口)</param>
/// <param name="id">主键值</param>
/// <returns>实体</returns>
public async Task<T> FetchAsync<T>(string dataName, long id)
where T : IEntity
{
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, String.Format("{0}?id={1}", DATA_URI, id));
request.Headers.Add(Phenix.Web.Client.Properties.Settings.Default.WebDataNameHeaderName, Uri.EscapeDataString(!String.IsNullOrEmpty(dataName) ? dataName : typeof(T).FullName));
HttpResponseMessage response = await _httpClient.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
throw new HttpRequestException(result);
return EntityHelper.JsonUnpack<T>(result);
}
/// <summary>
/// 获取实体集合
/// </summary>
/// <param name="dataName">数据名, 在服务端注册的实体集合类全名(需实现IEntityCollection接口)</param>
/// <param name="criteriaName">条件名, 在服务端定义的条件类全名(需实现ICriteria接口)</param>
/// <param name="criteria">JSON格式条件对象, 在服务端将被反序列化为criteriaName指定条件类的对象</param>
/// <param name="pageSize">分页大小</param>
/// <param name="pageNo">分页号, 从1起始</param>
/// <returns>实体集合</returns>
public async Task<T> FetchListAsync<T>(string dataName, string criteriaName, object criteria, int? pageSize, int? pageNo)
where T : IEntityCollection
{
HttpRequestMessage request;
if (criteria != null)
{
if (String.IsNullOrEmpty(criteriaName))
{
Type criteriaType = criteria.GetType();
if (!criteriaType.IsClass)
throw new ArgumentNullException("criteriaName");
criteriaName = criteriaType.FullName;
}
request = new HttpRequestMessage(HttpMethod.Get, String.Format("{0}?id={1}{2}",
DATA_URI,
Uri.EscapeDataString(criteria is string ? (string)criteria : (criteria is ICriteria ? CriteriaHelper.JsonPack(criteria) : JsonConvert.SerializeObject(criteria))),
pageSize.HasValue && pageNo.HasValue ? String.Format("&pageSize={0}&pageNo={1}", pageSize, pageNo) : null));
request.Headers.Add(Phenix.Web.Client.Properties.Settings.Default.WebCriteriaNameHeaderName, Uri.EscapeDataString(criteriaName));
}
else
request = new HttpRequestMessage(HttpMethod.Get, String.Format("{0}{1}",
DATA_URI,
pageSize.HasValue && pageNo.HasValue ? String.Format("?pageSize={0}&pageNo={1}", pageSize, pageNo) : null));
request.Headers.Add(Phenix.Web.Client.Properties.Settings.Default.WebDataNameHeaderName, Uri.EscapeDataString(!String.IsNullOrEmpty(dataName) ? dataName : typeof(T).FullName));
HttpResponseMessage response = await _httpClient.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
throw new HttpRequestException(result);
return EntityListHelper.JsonUnpack<T>(result);
}
检索到的数据,返回的是JSON格式的字符串,可以在客户端被转换成任何形式的数据。比如,象上述最后一行代码那样,由EntityListHelper提供的JsonUnpack()方法转换为实体集合对象。
21.1.2.1.2 调用
Fetch()函数的调用示例,代码摘录如下:
assemblyEasyList = client.DataProxy.FetchList<AssemblyEasyList>(
"Phenix.Test.使用指南._21._5.Business.AssemblyList",
"Phenix.Test.使用指南._21._5.Business.AssemblyCriteria2",
new LocalAssemblyCriteria()
{
Name = "Phenix.Test." //CriteriaOperate.Equal
});
上述黄色标示的代码段,如果被删除,WebAPI服务会查找业务类的ClassAttribute标签DefaultCriteriaType属性所申明的缺省条件类,来作为本次响应的查询类。如果未申明,则查找与业务类同名且带Criteria后缀的条件类,比如本示例找到的会是AssemblyCriteria类。这些申明的条件类,都应该实现ICriteria接口。
21.1.2.1.3 契约
在本示例中,被打包成JSON传递到服务端的查询对象,是本地客户端一个极为简单的对象:
class LocalAssemblyCriteria
{
public string Name { get; set; }
}
它只要和服务端所定义的查询类,在属性名称上能互相匹配就可以了:
[System.SerializableAttribute(), System.ComponentModel.DisplayNameAttribute("")]
public class AssemblyCriteria2 : Phenix.Business.CriteriaBase
{
[Phenix.Core.Mapping.CriteriaField(FriendlyName = "AS_NAME", Logical = Phenix.Core.Mapping.CriteriaLogical.And, Operate = Phenix.Core.Mapping.CriteriaOperate.Equal, TableName = "PH_ASSEMBLY", ColumnName = "AS_NAME")]
private string _name;
/// <summary>
/// AS_NAME
/// </summary>
[System.ComponentModel.DataAnnotations.Display(Name = "AS_NAME")]
[System.ComponentModel.DisplayName("AS_NAME")]
public string Name
{
get { return _name; }
set { _name = value; PropertyHasChanged(); }
}
}
甚至,我们可以直接传一个JSON字符串,也能达到同样的结果:
assemblyEasyList = client.DataProxy.FetchList<AssemblyEasyList>(
"Phenix.Test.使用指南._21._5.Business.AssemblyList",
"Phenix.Test.使用指南._21._5.Business.AssemblyCriteria2",
@"{""Name"":""Phenix.Test.""}"); //CriteriaOperate.Equal
最后,将执行过程中发生的Headers截录如下:
GET /api/Data?id=%7B%22Name%22%3A%22Phenix.Test.%22%7D HTTP/1.1
Phenix-Criteria-Name: Phenix.Test.%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97._21._5.Business.AssemblyCriteria2
Phenix-Data-Name: Phenix.Test.%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97._21._5.Business.AssemblyList
Phenix-Authorization: ADMIN,d48a36b5-31c9-41a6-ad35-efbcea458efc,2015-11-01 10:26:18,B7082E34BBFC95236B307E389C6796F7A29312A0D62F0BD683BF81541EB92044F8F4CE263C3AFC09116C14FD88564A4F41E5DFE1E0D755336F47465046E51CB0
Host: 10.0.0.11:8080
21.1.2.2 Save
21.1.2.2.1 方法
提交的数据,可以是单个实体,也可以是实体集合,且允许包含它们的树状结构。
提交的操作,不管是update还是insert、delete,使用的都是Post操作(需要对象能反映出当前的编辑状态):
/// <summary>
/// 提交实体
/// </summary>
/// <param name="dataName">数据名, 在服务端注册的实体类全名(需实现IEntity接口)</param>
/// <param name="entity">实体</param>
/// <returns>成功数量</returns>
public async Task<int> SaveAsync(string dataName, IEntity entity)
{
if (entity == null)
return 0;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, DATA_URI);
request.Headers.Add(Phenix.Web.Client.Properties.Settings.Default.WebDataNameHeaderName, Uri.EscapeDataString(!String.IsNullOrEmpty(dataName) ? dataName : entity.GetType().FullName));
request.Content = new StringContent(EntityHelper.JsonPackChanged(entity));
HttpResponseMessage response = await _httpClient.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
throw new HttpRequestException(result);
return (int)Utilities.ChangeType(result, typeof(int));
}
/// <summary>
/// 提交实体集合
/// </summary>
/// <param name="dataName">数据名, 在服务端注册的实体集合类/实体类全名(需实现IEntityCollection/IEntity接口)</param>
/// <param name="entityCollection">实体集合</param>
/// <returns>成功数量</returns>
public async Task<int> SaveAsync(string dataName, IEntityCollection entityCollection)
{
if (entityCollection == null)
return 0;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, DATA_URI);
request.Headers.Add(Phenix.Web.Client.Properties.Settings.Default.WebDataNameHeaderName, Uri.EscapeDataString(!String.IsNullOrEmpty(dataName) ? dataName : entityCollection.GetType().FullName));
request.Content = new StringContent(EntityListHelper.JsonPackChanged(entityCollection));
HttpResponseMessage response = await _httpClient.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
throw new HttpRequestException(result);
return (int)Utilities.ChangeType(result, typeof(int));
}
/// <summary>
/// 提交对象
/// </summary>
/// <param name="dataName">数据名, 在服务端注册的实体集合类/实体类全名(需实现IEntityCollection/IEntity接口)</param>
/// <param name="obj">JSON格式对象, 将被传到服务端, 需包含IsNew、IsSelfDeleted、IsSelfDirty属性以指明对象状态</param>
/// <returns>成功数量</returns>
public async Task<int> SaveAsync(string dataName, object obj)
{
if (obj == null)
return 0;
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, DATA_URI);
request.Headers.Add(Phenix.Web.Client.Properties.Settings.Default.WebDataNameHeaderName, Uri.EscapeDataString(dataName));
request.Content = new StringContent(obj is string ? (string)obj : JsonConvert.SerializeObject(obj));
HttpResponseMessage response = await _httpClient.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
throw new HttpRequestException(result);
return (int)Utilities.ChangeType(result, typeof(int));
}
函数返回的是成功提交的纪录数。如果未能成功提交,服务端返回的是异常提示信息,在客户端被封装在HttpRequestException异常对象里抛出。
21.1.2.2.2 调用
Save()函数的调用示例代码如下:
count = client.DataProxy.Save(
"Phenix.Test.使用指南._21._5.Business.Assembly",
new LocalAssembly()
{
IsSelfDirty = true,
AS_ID = assemblyId,
Caption = Sequence.Value.ToString()
});
21.1.2.2.3 契约
在本示例中,被打包成JSON传递到服务端的数据对象,是本地客户端一个极为简单的对象:
class LocalAssembly
{
/// <summary>
/// 新增状态
/// </summary>
public bool IsNew { get; set; }
/// <summary>
/// 删除状态
/// </summary>
public bool IsSelfDeleted { get; set; }
/// <summary>
/// 更新状态
/// </summary>
public bool IsSelfDirty { get; set; }
public long? AS_ID { get; set; }
public string Caption { get; set; }
}
上述黄色标示的代码段,是表示当前对象的编辑状态,这被用来告知服务端,应该如何提交本对象的数据。如果没有这些编辑状态属性,服务端将默认为这个对象处于IsSelfDirty = true状态。
数据对象,应该包含PrimaryKey属性(本示例为AS_ID属性),否则服务端是无法持久化这个对象的。而且,所有需要被持久化属性的名称,都应该和服务端的实体类定义互相匹配。
同查询对象一样,数据对象也可以直接传JSON字符串。
最后,将执行过程中发生的Headers截录如下:
POST /api/Data HTTP/1.1
Phenix-Data-Name: Phenix.Test.%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97._21._5.Business.Assembly
Phenix-Authorization: ADMIN,58733271-1be1-4232-a931-6110cc5f1d83,2015-11-01 11:11:04,E3774F2B6FF6406791108EA47BBE54A667BF9D249F6583F6AB5309081A68D7E54FC4E25BDB25661B522FAFC5BE75E74AD1B3F3D63F5C9B8039F7C93996A7B915
Content-Type: text/plain; charset=utf-8
Host: 10.0.0.11:8080
Content-Length: 108
Expect: 100-continue
21.1.2.3 Execute
21.1.2.3.1 方法
执行服务,实质上是CSLA的Command模式在WebAPI上的翻版,是把WebAPI当成执行服务引擎来使用,HTTP动词是Post:
/// <summary>
/// 执行服务
/// </summary>
/// <param name="serviceName">服务名, 在服务端注册的服务类全名(需实现IService接口)</param>
/// <param name="service">服务</param>
/// <returns>服务结果</returns>
public async Task<T> ExecuteAsync<T>(string serviceName, T service)
where T : IService
{
if (service == null)
throw new ArgumentNullException("service");
Type type = service.GetType();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, DATA_URI);
request.Headers.Add(Phenix.Web.Client.Properties.Settings.Default.WebDataNameHeaderName, Uri.EscapeDataString(!String.IsNullOrEmpty(serviceName) ? serviceName : type.FullName));
request.Content = new StringContent(JsonConvert.SerializeObject(service));
HttpResponseMessage response = await _httpClient.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
throw new HttpRequestException(result);
return (T)JsonConvert.DeserializeObject(result, type);
}
/// <summary>
/// 执行服务
/// </summary>
/// <param name="serviceName">服务名, 在服务端注册的服务类全名(需实现IService接口)</param>
/// <param name="obj">JSON格式对象, 将被传到服务端</param>
/// <returns>服务结果</returns>
public async Task<object> ExecuteAsync(string serviceName, object obj)
{
if (obj == null)
throw new ArgumentNullException("obj");
Type type = obj.GetType();
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, DATA_URI);
request.Headers.Add(Phenix.Web.Client.Properties.Settings.Default.WebDataNameHeaderName, Uri.EscapeDataString(serviceName));
request.Content = new StringContent(obj is string ? (string)obj : JsonConvert.SerializeObject(obj));
HttpResponseMessage response = await _httpClient.SendAsync(request);
string result = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
throw new HttpRequestException(result);
return obj is string ? result : JsonConvert.DeserializeObject(result, type);
}
这为我们开发WebAPI服务提供了强大的灵活性,可以纯粹以面向对象的形式编写服务端的业务逻辑代码:
[JsonObject(MemberSerialization.Fields)]
[System.SerializableAttribute()]
public class Service : Phenix.Core.Data.ServiceBase<Service> //Phenix.Business.CommandBase<Service>
{
public Service()
{
}
private string _assembly;
public Assembly Assembly
{
get { return Phenix.Core.Mapping.EntityHelper.JsonUnpack<Assembly>(_assembly); }
set { _assembly = Phenix.Core.Mapping.EntityHelper.JsonPack(value); }
}
/// <summary>
/// 处理执行指令(在运行持久层的程序域里被调用)
/// </summary>
protected override void DoExecute()
{
Assembly assembly = Assembly;
assembly.Caption = assembly.Caption + "被服务端改写";
Assembly = assembly;
EventLog.Save(Phenix.Core.Security.UserPrincipal.User, MethodBase.GetCurrentMethod(),
assembly.GetOldValue(Assembly.CaptionProperty) + "->" + assembly.Caption);
}
}
仅需继承自Phenix.Core.Data.ServiceBase,或者实现Phenix.Core.Mapping.IService接口:
21.1.2.3.2 调用
Execute()函数的调用示例代码如下:
Service service = client.DataProxy.Execute(
new Service()
{
Assembly = assembly
});
21.1.2.3.3 契约
在本示例中,客户端和服务端共享了同一个Service类,这在实际开发场景下,完全可以不必这么实现。同前文的做法一样,客户端可以使用一个简单的对象,将需要传递的属性命名与Service类的定义达成一致(本示例是Assembly属性)即可,或者直接传递JSON格式的字符串,都是可以得到相同的结果。
最后,将执行过程中发生的Headers截录如下:
POST /api/Data HTTP/1.1
Phenix-Data-Name: Phenix.Test.%E4%BD%BF%E7%94%A8%E6%8C%87%E5%8D%97._21._5.Business.Service
Phenix-Authorization: ADMIN,4534dfeb-35e0-44ab-963f-96c71b778cbc,2015-11-01 12:15:27,63A955F8056353BFDBDDE2FC1A8CF1D879DEA3C244EB64D1702810836F71FBCDEEAE66D0E5063542C765F2720649E076C07358A8A20A0D5B65EBC0CAFB3F72DE
Content-Type: text/plain; charset=utf-8
Host: 10.0.0.11:8080
Content-Length: 4489
Expect: 100-continue
21.1.3注册服务端事件
Phenixヾ的WebAPI服务不同于CSLA在remoting/WCF架构下的移动对象模式,不可能将数据封装成实体对象,而是纯粹的数据持久层服务(相对来说,在牺牲了业务框架特性的前提下,WebAPI服务在小批量数据下的Fetch和Save性能,理论上要优于remoting服务)。所以,业务类(继承自BusinessBase)上的服务端事件函数(比如OnSavingSelf系列函数等)都不可能被WebAPI服务调用到,业务规则(继承自ObjectRule)也不可能被WebAPI服务调用到。
为补偿因缺失上述这些业务框架特性所带来的服务端业务逻辑干预能力的弱化,Phenixヾ提供了一系列的服务端事件及其注册机制:
服务端事件,分为Fetch、Save处理过程的前事件和后事件,被定义在了IData接口里。
前事件可以拦截到客户端传递过来的数据(比如查询条件)并干预WebAPI数据服务的处理(或直接自行处理),而后事件可拦截到WebAPI数据服务的处理结果。
要注册上服务端事件,需用到AppHub的Data属性,Data属性实现了IData接口中一系列的服务端事件注册函数。
AppHub所在程序集是Phenix.Services.Library,我们开发的Business如需拦截服务端事件,就应该引用它:
服务端事件的注册和注销,都是在服务端被调用的,服务端事件的执行也应该运行在服务端。为此,我们要用到Phenixヾ提供的插件Host。具体方法是,在Business程序集里新增一个Plugin类,编译后添加到插件Host中:
Plugin类代码如下:
public class Plugin : PluginBase<Plugin>
{
public Plugin()
: base() { }
#region 方法
#region 覆写 PluginBase
/// <summary>
/// 启动
/// 由 PluginHost 调用
/// </summary>
/// <returns>确定启动</returns>
protected override bool Start()
{
AppHub.Data.FetchingRegister(typeof(Assembly), FetchingEventHandler);
AppHub.Data.FetchedRegister(typeof(Assembly), FetchedEventHandler);
AppHub.Data.FetchingRegister(typeof(AssemblyList), FetchingEventHandler);
AppHub.Data.FetchedRegister(typeof(AssemblyList), FetchedEventHandler);
AppHub.Data.SavingRegister(typeof(Assembly), SavingEventHandler);
AppHub.Data.SavedRegister(typeof(Assembly), SavedEventHandler);
AppHub.Data.SavingRegister(typeof(AssemblyList), SavingEventHandler);
AppHub.Data.SavedRegister(typeof(AssemblyList), SavedEventHandler);
return true;
}
/// <summary>
/// 暂停
/// 由 PluginHost 调用
/// </summary>
/// <returns>确定停止</returns>
protected override bool Suspend()
{
AppHub.Data.FetchingUnregister(typeof(Assembly), FetchingEventHandler);
AppHub.Data.FetchedUnregister(typeof(Assembly), FetchedEventHandler);
AppHub.Data.FetchingUnregister(typeof(AssemblyList), FetchingEventHandler);
AppHub.Data.FetchedUnregister(typeof(AssemblyList), FetchedEventHandler);
AppHub.Data.SavingUnregister(typeof(Assembly), SavingEventHandler);
AppHub.Data.SavedUnregister(typeof(Assembly), SavedEventHandler);
AppHub.Data.SavingUnregister(typeof(AssemblyList), SavingEventHandler);
AppHub.Data.SavedUnregister(typeof(AssemblyList), SavedEventHandler);
return true;
}
#endregion
/// <summary>
/// 检索前事件处理函数
/// </summary>
public void FetchingEventHandler(FetchEventArgs e)
{
EventLog.Save(Phenix.Core.Security.UserPrincipal.User, MethodBase.GetCurrentMethod(), CriteriaHelper.JsonPack(e.Criterions.Criteria));
}
/// <summary>
/// 检索后事件处理函数
/// </summary>
public void FetchedEventHandler(FetchEventArgs e)
{
EventLog.Save(Phenix.Core.Security.UserPrincipal.User, MethodBase.GetCurrentMethod(), e.Result);
}
/// <summary>
/// 提交前事件处理函数
/// </summary>
public void SavingEventHandler(SaveEventArgs e)
{
EventLog.Save(Phenix.Core.Security.UserPrincipal.User, MethodBase.GetCurrentMethod(), e.SourceJson);
}
/// <summary>
/// 提交后事件处理函数
/// </summary>
public void SavedEventHandler(SaveEventArgs e)
{
EventLog.Save(Phenix.Core.Security.UserPrincipal.User, MethodBase.GetCurrentMethod(), e.Result.ToString());
}
#endregion
}
插件添加到Host里会自动启动,Plugin对象的Start()函数会被调用到,也就执行了服务端事件的注册方法。插件启动后,可随时被暂停(Plugin对象的Suspend()函数会被调用到)。
服务端事件所传递的参数:
因这些参数都继承了ShallEventArgs,我们可以在XXXing事件里通过将e.Applied设置为true来告知WebAPI数据服务,本事件已自行处理了Fetch或Save操作。这时,事件代码里应该通过给e.Result赋值,将自行处理得到的返回结果传给WebAPI数据服务,WebAPI数据服务会将e.Result内容打包返回给HTTP客户端。
21.1.4案例与建议
测试工程“Phenix.Test.使用指南.21.5”已基本上覆盖了所有可能的数据操作方法,请编译后运行观察效果。
需注意的是,本测试工程在客户端也用到了业务类、实体类,与之相关的配置信息需要被下载到本地。而WebAPI仅提供的是数据操作服务,所以需要额外通过调用Phenixヾ的remoting/WCF服务来下载他们的配置信息:
Phenix.Services.Client.Library.Registration.RegisterWorker(NetConfig.LocalAddress);
在实际开发场景中,客户端如果能够用上remoting/WCF服务的话,就没必要使用WebAPI技术。而一旦使用了WebAPI技术,客户端就没必要用上业务类、实体类。所以,也就不需要上述代码来获取配置信息了。