转自:http://www.cnblogs.com/NineFlowers/archive/2009/07/31/1535174.html#_Appendix_A_–
.NET RIA Services 框架可以轻松建立一个有ASP.net中间层和一个或多个Silverlight客户层组成的多层应用程序。该框架允许开发者在中间层公布特定域的实体和操作,同时能在客户端进行访问。
中间层公布的实体和操作是通过在Silverlight客户端工程自动生成的客户代理类来访问的。
下面章节将描述代码生成机制。
应用示例
我们用一个小的应用来开始讨论这个复杂的机制,揭开这个神秘层的面纱,整个过程我们让其保持尽可能的简单:
使用模板 "File | New Project | Silverlight Application"来创建应用程序。
City实体和存储
这个例子我们使用纯CLR对象实体,命名为City,下面是City.cs类:
using System;
using System.ComponentModel.DataAnnotations;///译注:注意这个是Ria的程序集,一定要添加相关程序集,否则此处无法引用
namespace SilverlightApplication1.Web.Cities
{
public partial class City
{
[Key] //译注:DomainService公布类属性必须至少要有一个Key特性的属性。
public string Name {get;set;}
[Key]
public string State { get; set; }
}
}
接着我们使用一个CityData.cs文件来做一个简单的内存对象来进行存储:
using System;
using System.Collections.Generic;
namespace SilverlightApplication1.Web.Cities
{
public class CityData
{
private List<City> _cities = new List<City>( new City[] {
new City() { Name="Redmond", State="WA" },
new City() { Name="Bellevue", State="WA" }
});
public IList<City> Cities
{
get
{
return this._cities;
}
}
}
}
显然,实际应用中我们会使用数据库,之所以不使用那些复杂的东西可以让我们更容易理解这中间的过程。
体验DomainService
ASP.NET 服务端工程可以通过DomainService类访问特定域的实体和操作。下面是我们创建的这个类用来只读访问City实体。
namespace SilverlightApplication1.Web.Cities
{
using System;
using System.Collections.Generic;
using System.Web.Ria;
using System.Web.DomainServices;
//可以通过模板来添加,也可以通过手工引用相关的程序集(Ria相关。。)
[EnableClientAccess()]
public class CityService : DomainService
{
private CityData _cityData = new CityData();
public IEnumerable<City> GetCities()
{
return this._cityData.Cities;
}
}
}
自动生成的代码
编译解决方案,发现在Silverlight客户端工程目录下生成了一个文件,选择silverlight客户端工程并且单击按钮"Show All Files"可以查看到该文件,如下图。
完整的代码见3.4"自动生成的代码"(链接: Generated Code. ),你要注意下面两个重要的东西:
- 生成的代码定义了一个和服务器端City类在结构外观(shape)和命名空间上一致。
- 生成的代码定义了一个CityContex上下文类,并公布一个GetCitiesQuery() 方法. 通过传递该方法的返回值给DomainContext的Load方法,我们可从服务端定义的DomainService--CityService取得City实体,更多细节看 "如何查询实体"章节(链接:How to query entities).
给实体添加元数据(Adding metadata to the entity)
此时运行应用程序,Silverlight应用工程可以使用CityContex和City实体类型,但我们想示例代码生成的更多内容:元数据和共用文件(share files).
这里,我们想在中间层和客户端添加校验规则,因此我们将建立一个新的meatadata类(被亲切地称之为友类(buddy Class)---该如何翻译呢?),如下:
注意这个文件或类名并不重要,但这样的命名模式可以便于理解。
City.metadata.cs 文件的代码如下:
using System;
using System.ComponentModel.DataAnnotations;
namespace SilverlightApplication1.Web.Cities
{
[MetadataType(typeof(City.CityMetadata))]
public partial class City
{
internal sealed class CityMetadata
{
[Required]
public string Name;
[Required]
[StringLength(2, MinimumLength = 2)]
public string State;
}
}
}
Metadata类提供一种方便的方式将元数据绑定到一个实体,而不要修改实体类本身的相应成员。这方面的内容将在"如何使用元数据类"章节描述 (链接:How to use metadata classes)
使用元数据类规则如下:
- 实体类必须使用MetadataType特性以指定关联的元数据类
- 元数据类必须仅包括成员,并且成员名字和实体类成员一致
元数据类可以省略不需要扩展到成员,也不能包含不在实体类中的成员。成员需要的只是名称一致,.元数据成员不必和实体成员具有相同的类型,如果这样只是为了具有易读性。
实体类中的每个成员在元数据类中有一致的成员,并通过自定义的特性进行关联。元数据类仅存在于服务器端,当代码自动生成的客户端代理类的时候这些自定义特性合并到生成的类中。
添加共用代码(Adding shared code)
这里开始阐述客户端代码生成处理的更多特征,以及服务端和客户端工程共用的文件("Shared"翻译成"共用"是因为服务端和客户端各有一份代码,而"共享"让人感觉只有有一份多人共用之嫌疑),这些更多细节在"如何跨层共用代码"章节描述(链接: "How to Share Code Across Tiers".)
建立一个 City.shared.cs文件.这个命名模式很重要,因为这样的名字能使代码生成任务能识别该文件是用来共用的,而 文件的位置并不重要,但必须放在服务器的文件列表里,这里将它放置在文件夹 "Shared Code".如下:
下面是我们在 City.shared.cs 文件中写的代码:
using System;
namespace SilverlightApplication1.Web.Cities
{
public partial class City
{
public string FullName
{
get
{
return this.Name + ", " + this.State;
}
}
}
}
该文件建立了一个City实体的计算属性,该属性在客户层和服务器层都可见,通过这种方式我们既没有改变实体的结构也没有增加活动实体的大小。但从涉及到两层的应用程序逻辑上看,City实体获得一个新的属性。
最终产生的代码(The final generated code)
重新编译,会看到客户端产生的文件
注意这些新增的文件:
- 附加在City实体的验证特性通过中间层的元数据类衍生到Silverlight客户端层的生成代码中了
- 我们在中间层建立的共用文件被一字不变的生成到客户端工程,包括文件夹结构
这是如何做到的(How it works)
这是如何做到的呢。代码生成必须满足以下两个条件:
- 安装有.NET RIA Services framework
- Silverlight客户端工程有和服务端工程建立RIA链接( "RIA Link" )
满足上面两个条件,编译这个解决方案中的任一个工程都会导致代码生成。
在"理解多层Silverlight工程"章节我们将描述"RIA LINK"。
代码生成运算法则(The code-generation algorithms)
代码生成通过检查服务端范围内的所有程序集引用开始,伪代码类似如下::
Foreach (Assembly either built or referenced)
Foreach (DomainService marked with [EnableClientAccess])
Generate a DomainContext class on the Silverlight client
Foreach (Entity exposed by DomainService)
Generate entity proxy class on the Silverlight client
换句话说:
- 所有服务端工程中引用的或建立的程序集都会被解析
- 所有继承 DomainService并标记 [EnableClientAccess] 自定义特性的都要被解析
- 所有的DomainService公共方法都会被解析,确定哪些实体类型要公布
如果服务端没有标记[EnableClientAccess]自定义特性的公共DomainService,就不会生成代码。
生成的实体代理类(Generated entity proxy classes)
生成的实体代理类,具有如下特征:
- 实体代理类具有和服务端实体类相同的命名空间
- 实体代理类具有和服务端实体类一致的名字
- 服务端实体类的公共属性将在实体代理类中公布
-
产生的实体代理类属性setter代码:
- 激活INotifyPropertyChanged通知,允许数据绑定
- 执行验证(Perform validation)
- 绑定到实体类或属性上的自定义特性衍生到代理类.
生成的自定义特性(Generated custom attributes)
附属到实体类或实体类的任意属性上的自定义特性能给客户端层提供有用的元数据,包括例如验证,UI提示,[Key]fileds的指示等,代码生成器将因此衍生实体类的自定义特性到代理类中,遵循如下规则:
- 自定义特性类型本身必须存在客户端工程中
- 以特性描述的类型必须存在客户端
-
自定义特性类型结构必须如下
- 为所有属性公布公共setter,或者
- 公布构造器,让所有没有setters的属性能指定
简言之,代码生成器衍生自定义属性的时候在客户端不允许有编译错误。
然而,如果你检查生成的代码,如果有你需要的自定义特性没有在代理类中衍生,请检查你客户端工程的引用的程序集,添加需要的程序集,重新编译工程。
元数据类(The metadata class)
当创建实体的代理类时,代码生成器自动插入和其关联的元数据类的验证特性。
客户端不会出现元数据类, 然而,生成的代理类会反映出这些内容
更多细节参靠"如何使用元数据类"(链接: "How to use metadata classes".)
DomainContex类
除了每个实体类的对应代理类外,生成的文件还包括一个对应的DomainContext类,代码中CityContex类就是生成的DomainContex类
代码生成机制检查每一个服务端DomainService类的公共方法,并按下面规则生成DomainContex类:
- DomainContext类使用其DomainSerice类相同的命名空间名
-
产生3个构造器:
- 缺省的构造器,内建通过http与DomainService通信必须的URI
- 允许客户端指定可选择的URI 构造器
- 允许客户端提供自定义DomainClient作为参数的构造器(典型的应用与单元测试或自定义传输层)
- 每个DomainService类中的查询(query)方法在DomainClient类中都会生成相应的实体查询方法. 例如,DomainService 中的 GetCities()方法生成CityContex类中的GetCitiesQuery() 方法,该方法的返回值通过DomainContext.Load()方法请求中间层的GetCities()查询.
- DomainService中自定义的方法和服务操作在DomainContex代理类中产生相应的方法
- DomainService methods that perform inserts, updates, or deletes cause the generated EntityLists in the DomainContext to be constructed with flags indicating which of these operations are permitted on the client.(按这里的意思DomainService公布的插入修改删除等操作在DomainContext文件中生成的实体列表中体现出来,表明允许客户端操作,但在我测试生成的文件中并非如此?理解有误?)
关于DomainContext更多细节参考如下章节:
"How to query entities"
"How to modify entities"
"Using Custom Methods and Service Operations"
共用文件的拷贝规则(The shared file copy algorithms)
在代码生成阶段,所有命名如*.shared.cs or *.shared.vb 模式的将原封不动地拷贝到客户端工程,(包括文件夹结构),所有这些都是简单拷贝。
共用代码的自动检测(The automatic detection of shared code)
为了避免代理类的重复定义,代码生成器必须检测客户端工程中已经存在的类型和成员。
我们示例中,使用了 City.shared.cs文件给City实体增加了FullName属性,由于文件名的命名其原封不动地拷贝到客户端工程中, In other words, even without proxy class code generation, this property was pre-defined in the City class on both tiers. If the code generator were to generate a property getter/setter pair in the entity proxy class for this property, it would create a compilation error.
因此,当代码生成器生成客户端代理类和property getter/setter的时候,它自动判断哪个类型和成员已经存在于客户端避免重复生成。
更多细节见"如何共用跨层代码"链接: "How to Share Code Across Tiers".
小结(Summary)
微软 .NET RIA Services framework 简化了创建链接Asp.net和Silverlight多层平台的工作。
应用程序开发者使用DomainService类公布特定域实体和操作,框架自动在Silverlight客户端工程产生相应的代理类,开发者在silverlight客户端工程中编写代码,通过产生的DomainContex类交互。
.
生成的代码(Generated code)
下面是客户端工程中生成的完整代码:
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:2.0.50727.3053
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace SilverlightApplication1
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web.Ria.Data;
using System.Windows.Ria.Data;
/// <summary>
/// Context for the RIA application.
/// </summary>
/// <remarks>
/// This context extends the base to make application services and types available
/// for consumption from code and xaml.
/// </remarks>
public sealed partial class RiaContext : System.Windows.Ria.RiaContextBase
{
#region Extensibility Method Definitions
/// <summary>
/// This method is invoked from the constructor once initialization is complete and
/// can be used for further object setup.
/// </summary>
partial void OnCreated();
#endregion
/// <summary>
/// Initializes a new instance of the RiaContext class.
/// </summary>
public RiaContext()
{
this.OnCreated();
}
/// <summary>
/// Gets the context that is registered as a lifetime object with the current application.
/// </summary>
/// <exception cref="InvalidOperationException"> is thrown if there is no current application,
/// no contexts have been added, or more than one context has been added.
/// </exception>
/// <seealso cref="Application.ApplicationLifetimeObjects"/>
public new static RiaContext Current
{
get
{
return ((RiaContext)(System.Windows.Ria.RiaContextBase.Current));
}
}
}
}
namespace SilverlightApplication1.Web.Cities
{
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.Serialization;
using System.Web.Ria.Data;
using System.Windows.Ria.Data;
[DataContract(Namespace="http://schemas.datacontract.org/2004/07/SilverlightApplication1.Web.Cities")]
public sealed partial class City : Entity
{
private string _name;
private string _state;
#region Extensibility Method Definitions
/// <summary>
/// This method is invoked from the constructor once initialization is complete and
/// can be used for further object setup.
/// </summary>
partial void OnCreated();
partial void OnNameChanging(string value);
partial void OnNameChanged();
partial void OnStateChanging(string value);
partial void OnStateChanged();
#endregion
/// <summary>
/// Default constructor.
/// </summary>
public City()
{
this.OnCreated();
}
[DataMember()]
[Key()]
[Required()]
public string Name
{
get
{
return this._name;
}
set
{
if ((this._name != value))
{
this.ValidateProperty("Name", value);
this.OnNameChanging(value);
this.RaiseDataMemberChanging("Name");
this._name = value;
this.RaiseDataMemberChanged("Name");
this.OnNameChanged();
}
}
}
[DataMember()]
[Key()]
[Required()]
[StringLength(2, MinimumLength=2)]
public string State
{
get
{
return this._state;
}
set
{
if ((this._state != value))
{
this.ValidateProperty("State", value);
this.OnStateChanging(value);
this.RaiseDataMemberChanging("State");
this._state = value;
this.RaiseDataMemberChanged("State");
this.OnStateChanged();
}
}
}
public override object GetIdentity()
{
return EntityKey.Create(this._name, this._state);
}
}
public sealed partial class CityContext : DomainContext
{
#region Extensibility Method Definitions
/// <summary>
/// This method is invoked from the constructor once initialization is complete and
/// can be used for further object setup.
/// </summary>
partial void OnCreated();
#endregion
/// <summary>
/// Default constructor.
/// </summary>
public CityContext() :
this(new HttpDomainClient(new Uri("DataService.axd/SilverlightApplication1-Web-Cities-CityService/", System.UriKind.Relative)))
{
}
/// <summary>
/// Constructor used to specify a data service URI.
/// </summary>
/// <param name="serviceUri">
/// The CityService data service URI.
/// </param>
public CityContext(Uri serviceUri) :
this(new HttpDomainClient(serviceUri))
{
}
/// <summary>
/// Constructor used to specify a DomainClient instance.
/// </summary>
/// <param name="domainClient">
/// The DomainClient instance the DomainContext should use.
/// </param>
public CityContext(DomainClient domainClient) :
base(domainClient)
{
this.OnCreated();
}
public EntityList<City> Cities
{
get
{
return base.Entities.GetEntityList<City>();
}
}
/// <summary>
/// Returns an EntityQuery for query operation 'GetCities'.
/// </summary>
public EntityQuery<City> GetCitiesQuery()
{
return base.CreateQuery<City>("GetCities", null, false, true);
}
protected override EntityContainer CreateEntityContainer()
{
return new CityContextEntityContainer();
}
internal sealed class CityContextEntityContainer : EntityContainer
{
public CityContextEntityContainer()
{
this.CreateEntityList<City>(EntityListOperations.None);
}
}
}
}