2010年1月24日星期日

WCF RIA 服务 (十七)- 数据 7

数据模型中的继承
WCF RIA Services允许我们使用做为继承体系中的一部分的实体。一个继承模型包含了一个从其他数据类派生的数据类。例如,一个多态继承模型可以包含一个Customer实体和两个从Customer派生的实体(PublicSectorCustomer和PrivateSectorCustomer)。通过RIA Services,我们可以在domain Services中写一个返回一个根类型的集合和从根类型派生的类型的查询方法。或者,可以写一个仅返回派生类型集合的查询方法。还可以写修改根类型或任何派生类的操作方法。
注:只在VS2010和SL4中使用RIA Services时支持继承,在VS2008和SL3中不支持。

数据模型
在服务端项目中,我们可以像定义其他数据类那样来为继承模型定义数据类。使用的这个对象模型既可以是从数据层中自动生成的类也可以是手动创建的数据类。
我们不必非要通过domain service来公开整个层次。相反,在domain service中公开的层次中最后派生的类,是与客户端交互的根类。从根类型派生出的类型可以向客户端公开,但父类型不必被公开。在根类中,必须把想要公开的的任意派生类型包含在KnownTypeAttribute属性中。如果想要忽略派生类型,可以不把它包含在KnownTypeAttribute属性中。下面示例了一个包含基类Customer,和两个派生类PrivateSectorCustomer,PublicSectorCustomer的手工创建的数据模型。两个派生类会包含在Customer类的KnownTypeAttribute属性下,因为Customer是数据操作的根类型。

[KnownType(typeof(PublicSectorCustomer)), KnownType(typeof(PrivateSectorCustomer))]
public class Customer
{
[Key]
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address { get; set; }
public string City { get; set; }
public string StateProvince { get; set; }
public string PostalCode { get; set; }
[Association("CustomerOrders", "CustomerID", "CustomerID")]
public List Orders { get; set; }
}

public class PublicSectorCustomer : Customer
{
public string GSARegion { get; set; }
}

public class PrivateSectorCustomer : Customer
{
public string CompanyName { get; set; }
}

多态查询
定义了数据模型后,我们创建一个对客户端公开类型的domain service。当在一个查询方法中公开一个类型时,可以返回这个类型和任意派生的类型。例如,一个返回Customer实体集合的查询可以包含一个PrivateSectorCustomer对象和PublicSectorCustomer对象。还可以指定一个只返回一个派生类型的查询方法。下面示例了返回不同类型的查询方法。

public IQueryable GetCustomers()
{
return context.Customers;
}

public IQueryable GetCustomersByState(string state)
{
return context.Customers.Where(c => c.StateProvince == state);
}

public IQueryable GetCustomersByGSARegion(string region)
{
return context.Customers.OfType().Where(c => c.GSARegion == region);
}

public IQueryable GetPrivateSectorByPostalCode(string postalcode)
{
return context.Customers.OfType().Where(c => c.PostalCode == postalcode);
}

为客户端项目生成代码
当生成解决方案时,在客户端会为已经在domain service中公开的继承体系生成代码。体系的根类被生成并派生于Entity类。每个派生的类都是生成和派生于根类。在DomainContext类中,只生成一个Entity(TEntity)成员属性,并且它需要根类型的对象。为每一个查询都生成了EntityQuery对象,它返回在domain service操作中指定的类型。
下面示例了一个在客户端为查询方法生成的简单版本的代码。它并没有包含所有生成类中的代码,只是想强调一些重要的成员属性和方法。

[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ExampleApplication.Web")]
[KnownType(typeof(PrivateSectorCustomer))]
[KnownType(typeof(PublicSectorCustomer))]
public partial class Customer : Entity
{
public string Address { get; set; }
public string City { get; set; }
[Key()]
public int CustomerID { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public string PostalCode { get; set; }
public string StateProvince { get; set; }

public override object GetIdentity();

}

[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ExampleApplication.Web")]
public sealed partial class PrivateSectorCustomer : Customer
{
public string CompanyName { get; set; }
}

[DataContract(Namespace="http://schemas.datacontract.org/2004/07/ExampleApplication.Web")]
public sealed partial class PublicSectorCustomer : Customer
{
public string GSARegion { get; set; }
}

public sealed partial class CustomerDomainContext : DomainContext
{
public CustomerDomainContext();
public CustomerDomainContext(Uri serviceUri);
public CustomerDomainContext(DomainClient domainClient);

public EntitySet Customers { get; }

public EntityQuery GetCustomersQuery();
public EntityQuery GetCustomersByGSARegionQuery(string region);
public EntityQuery GetCustomersByStateQuery(string state);
public EntityQuery GetPrivateSectorByPostalCodeQuery(string postalcode);
}

数据修改
可以为继承体系中的更新、插入和删除对象添加domain service方法。如同查询方法,可以为操作指定一个根类型或一个派生类型。然而,任何对派生类的更新、插入或删除操作也都应该可以在根类型中执行。可以在体系中为任何类型添加named updat
方法。会为在方法中指定的类型在客户端生成相应的named update方法。
下面的代码示例了两个更新方法和一个named update方法的签名。

public void UpdateCustomer(Customer customer) { /* implement */ }
public void UpdatePublicSectorCustomer(PublicSectorCustomer customer) { /* implement */ }
public void EnrollInRewardsProgram(PrivateSectorCustomer customer) { /* implement */ }

关联
可以在根类或派生类中定义关联。我们在练歌数据类之间应用AssociationAttribute属性来定义一个关联。在数据模型的例子中,在Customer和Order之间定义了一个关联。如果对根类型应用了关联,那么所有的派生类型也包含这个关联。
使用继承的基本规则


  • 仅对实体类型支持继承,非实体类型被当做在domain service操作签名中指定的类型。
  • 在domain service操作中的返回值和参数,不支持接口类型。
  • 在代码生成的时候必须知道继承体系中的类型集。在生成代码的时候没有指定返回类型的行为是未指明和实现相关的。
  • 允许对实体类型的公共成员属性或字段使用virtual或new。但在客户端对应生成的实体类型中会被忽略。
  • 不允许对domain service操作方法重载。
  • 与继承相关的LINQ查询功能不能用来运行domain service方法。特别地,不支持OfType>T<,is,as和GetType()操作符和方法。然而,在LINQ to Objects查询中的EntitySet或EntityCollection(TEntity)上直接使用这些操作符。

实体继承体系

定义继承体系使用下面的规则:

  • 使用System.Runtime.Serialization.KnownTypeAttribute属性来指定已知的类型。对RIA Services来说,需要使用包含一个System.Type参数并具有KnownTypeAttribute的构造函数。
  • 体系中的已知类型,必须在domain service公开的体系中的根类型中指定。RIA Services 不支持在派生类型上声明类型为已知。
  • 每个在已知类型集中的类都必须是public。
  • 在体系中的一个或多个类可以是abstract的。
  • 当声明已知类型时,可以在体系中省略一个或多个类。
  • 必须在根类中指定关键成员。
  • 关联的声明和使用是不可改变的。

DomainService操作

在继承体系中使用下面规则定义domain service 操作

  • 在体系中至少有一个对应于根类型的查询方法。其他的查询操作可以使用派生类型作为返回值。
  • 对返回多态结果的方法,查询方法可以返回一个根类型。
  • 如果对系统内的类型定义了更新、插入、或删除操作,那么对根类型也要定义相同的操作。不可能只对某些类型选择操作。
  • 自定义操作可以使用根类型或派生类型作为实体参数。当实例的实际类型是从自定义操作中的类型派生的时候,操作是可行的。
  • DomainServiceDescription类返回对给定类型最适用的更新、插入、删除和查询方法。

TpyeDescriptionProvider

下面的规则应用于TypeDescriptionProvider(TDP)

  • 当体系中的根类型通过一个查询方法或IncludeAttribute属性公开时,会对LINQ to SQL应用TpyeDescriptionProvider,并且实体框架会自动对实体使用KnownType属性声明。而对派生类型则不会如此。
  • 在Add New Domain Service Class对话框中不会提供任何继承体系的功能。用户可以在体系中选择一些、全部或不选择类型。

生成的代码

对体系中的实体,以下的规则应用于客户端的生成代码

  • 对每个继承体系,只生成一个EntitySet(TEntity)类。在已知的体系中EntitySet(TEntity)的类型参数是最基本的类型。
  • 对体系中每个已知的类型,都对应生成一个实体类型。即使没有domain service方法公开这个类型,这个实体类型也会生成。
  • 对指定的类型可生成自定义方法,并这些方法可用于任何派生类型。
  • 构造函数是根据继承体系来绑定的。每个类型都可以调用OnCreated方法并可以自定义此方法。
  • 如果服务端的实体体系中的类被跳过,那么在客户端生成的实体体系中也会被跳过。

WCF RIA 服务 (十六)- 表示模型

WCF RIA Services允许我们创建数据模型来综合从数据访问层得到的不同实体数据。这个模型就是表示模型。当我们不想把数据层的数据直接公开给客户端时,会使用这个特性。当使用表示模型时,可以只修改表示模型而不是客户端来回应数据访问层中的改动。还可以设计一个综合那些仅与客户端用户相关的字段的模型,来简化客户端代码。
创建表示模型
需要用来维护数据完整性的数据库结构可能会比在客户端应用中需要的那些实体更复杂。我们可以通过把那些与应用相关的字段组合进一个表示模型,来简化这个数据结构。例如,在AdventureWorksLT示例数据库中,我们通过Customer,CustomerAddress,Address表来检索客户和地址数据。
通过在服务端项目中创建一个类来创建表示模型,并定义需要的成员属性。这些定义的成员属性对应着你想从实体公开的成员属性。例如下面的示例,在服务端创建一个类,来表示那些从Customer、CustomerAddress、Address表中仅仅需要的字段。
在表示模型中查询和修改数据
在创建了表示模型后,通过添加一个和这个表示类型交互的domain service来向客户端公开。下面的示例展示了一个从DomainService类派生的域服务。

[EnableClientAccess()]
public class CustomerDomainService : DomainService
{
AdventureWorksLT_DataEntities context = new AdventureWorksLT_DataEntities();
}

为了检索数据,我们在Domain service中添加了一个query方法。在query方法中,从数据访问层中的实体检索相关的数据,并把这些值赋值给新的表示模型实例中对应的成员属性。从这个query方法,要么返回一个这个表示模型类型的实例,要么返回一个表示模型类型的IQueryable。下面示例了Customer表示模型的查询方法。

public IQueryable GetCustomersWithMainOffice()
{
return from c in context.Customers
join ca in context.CustomerAddresses on c.CustomerID equals ca.CustomerID
join a in context.Addresses on ca.AddressID equals a.AddressID
where ca.AddressType == "Main Office"
select new CustomerPresentationModel()
{
CustomerID = c.CustomerID,
FirstName = c.FirstName,
LastName = c.LastName,
EmailAddress = c.EmailAddress,
Phone = c.Phone,
AddressType = ca.AddressType,
AddressLine1 = a.AddressLine1,
AddressLine2 = a.AddressLine2,
City = a.City,
StateProvince = a.StateProvince,
PostalCode = a.PostalCode,
AddressID = a.AddressID,
AddressModifiedDate = a.ModifiedDate,
CustomerModifiedDate = c.ModifiedDate
};
}

由于在数据访问层中并没有通过domain service公开实体(Customer、CustomerAddress、Address),所以不会在客户端生成这些类型。相反,在客户端仅生成表示模型类型。
如果想通过表示模型更新数据,我们需创建一个更新方法,并定义从表示模型向实体存贮值的逻辑。可以参考本节最后的示例。
把值返回给客户端
提交了更改后,我们可能需要把存贮到中间层逻辑或数据源中的值传回客户端。WCF RIA Services提供了Associate(TEntity,TStoreEntity)方法来映射从实体回到表示模型的值。在这个方法中,我们提供一个在提交后调用的回调方法。在这个回调方法中,我们把在中间层已改动的值赋值给表示模型。通过执行这个步骤来使客户端持有当前值。
下面是示例演示了如何更新实体中的值,并如何把修改后的数据映射回表示模型。

[Update]
public void UpdateCustomer(CustomerPresentationModel customerPM)
{
Customer customerEntity = context.Customers.Where(c => c.CustomerID == customerPM.CustomerID).FirstOrDefault();
CustomerAddress customerAddressEntity = context.CustomerAddresses.Where(ca => ca.CustomerID == customerPM.CustomerID && ca.AddressID == customerPM.AddressID).FirstOrDefault();
Address addressEntity = context.Addresses.Where(a => a.AddressID == customerPM.AddressID).FirstOrDefault();

customerEntity.FirstName = customerPM.FirstName;
customerEntity.LastName = customerPM.LastName;
customerEntity.EmailAddress = customerPM.EmailAddress;
customerEntity.Phone = customerPM.Phone;
customerAddressEntity.AddressType = customerPM.AddressType;
addressEntity.AddressLine1 = customerPM.AddressLine1;
addressEntity.AddressLine2 = customerPM.AddressLine2;
addressEntity.City = customerPM.City;
addressEntity.StateProvince = customerPM.StateProvince;
addressEntity.PostalCode = customerPM.PostalCode;

CustomerPresentationModel originalValues = this.ChangeSet.GetOriginal(customerPM);

if (originalValues.FirstName != customerPM.FirstName ||
originalValues.LastName != customerPM.LastName ||
originalValues.EmailAddress != customerPM.EmailAddress ||
originalValues.Phone != customerPM.Phone)
{
customerEntity.ModifiedDate = DateTime.Now;
}

if (originalValues.AddressLine1 != customerPM.AddressLine1 ||
originalValues.AddressLine2 != customerPM.AddressLine2 ||
originalValues.City != customerPM.City ||
originalValues.StateProvince != customerPM.StateProvince ||
originalValues.PostalCode != customerPM.PostalCode)
{
addressEntity.ModifiedDate = DateTime.Now;
}

context.SaveChanges();

this.ChangeSet.Associate(customerPM, customerEntity, MapCustomerToCustomerPM);
this.ChangeSet.Associate(customerPM, addressEntity, MapAddressToCustomerPM);
}

private void MapCustomerToCustomerPM(CustomerPresentationModel customerPM, Customer customerEntity)
{
customerPM.CustomerModifiedDate = customerEntity.ModifiedDate;
}

private void MapAddressToCustomerPM(CustomerPresentationModel customerPM, Address addressEntity)
{
customerPM.AddressModifiedDate = addressEntity.ModifiedDate;
}

2010年1月22日星期五

WCF RIA 服务 (十五)- 数据 5

层的组成
WCF RIA Services允许我们为具有层次概念的数据类创建应用逻辑,例如SalesOrderHeader实体和SalesOrderDetail实体。这样相关实体就组成了所谓的层次。定义了类之间的组成关系后,就可以像操作一个单一个体一样来操作对实体的数据修改,而不是像操作独立实体那样。这就会简化中间层的逻辑,因为我们可以对整个实体层来写应用逻辑,而不是把逻辑拆分对应每个实体并在数据操作时企图协调这些拆分的逻辑。
了解层的概念
在实体的层级概念中,一个实体被称为父实体,其他关联的实体被称为子实体。父实体是表示数据的类,是那些子实体数据的的根。例如,SalesOrderHeader实体是父实体,SalesOrderDetail是子实体。SalesOrderHeader实体内的一个记录可以和SalesOrderDetail实体内的多个记录连接起来。
作为层次关系一部分的数据类通常具有如下特征:


  • 这些实体间的关系可以表示为一个有子实体和一个父实体的树型结构。子实体可以扩展为任何数量的级别。
  • 子实体的生命周期是包含在父实体的生命周期内。
  • 子实体如果离开了父实体的上下文,就没有了有意义的身份。
  • 需要把实体看做单一个体来对实体进行数据操作。例如,添加、删除、或更新子实体内的一个记录,需要在父实体内也有相应的改动。

定义一个组成关系

要在实体间定义层次关系,可以对在实体间表示关联的成员属性应用CompositionAttribut属性。下面的示例展示了如何通过元数据类来在SalesOrderHeader和SalesOrderDetail之间定义层次关系。CompositionAttribute属性在System.Web.Ria.Data命名空间内。需要用using来引用这个命名空间。


[MetadataTypeAttribute(typeof(SalesOrderHeader.SalesOrderHeaderMetadata))]
public partial class SalesOrderHeader
{
internal sealed class SalesOrderHeaderMetadata
{
private SalesOrderHeaderMetadata()
{
}

[Include]
[Composition]
public EntitySet SalesOrderDetails;

}
}

当对成员属性应用CompositionAttribute属性时,子实体内的数据不能从父实体自动寻回。为了在查询结果中包含子实体,应该对表示子实体的成员属性应用IncludeAttribute属性,并在查询方法中包括子实体。可以参考最后的例子。

Domain Service操作组合的层次关系

当定义了组合层次后,需要改变父实体和子实体交互的方式。包含在Domain Service中的逻辑必须解释实体间的联系。通常,通过对父实体的Domain Service方法来定义层次的逻辑。在对父实体的Domain Service操作里,处理对父实体和子实体的更改。

下面的规则应用于Domain Service操作来操作有层次关系的实体。

  • 允许对父和子实体使用查询方法,但推荐在父实体的上下文内查询子实体。如果修改没有通过父实体装载的子实体会抛出一个异常
  • 可以添加对子实体的数据修改操作,但这些操作可以被父实体内的操作影响。

    • 如果允许对父实体更改,那么更改、插入、和删除操作被允许在子实体上。


    • 如果父实体有一个命名的更新named updated方法,那么所有子实体都必须允许更新操作。


    • 如果在父实体允许插入、删除,那么在子实体也应该允许对应的操纵。


在客户端,对有层次关系的实体应用下面的规则。

  • 当子实体包含一个改动,这个改动要通知给父实体。父实体上的HasChanges应设置为true。
  • 当一个父实体被改动,所有的子实体(包括没有改动的子实体)都被包括在改动集合中。
  • 在客户端的子实体中不会在Domain上下文中生成public的EntitySet,必须通过父实体来访问子实体。
  • 一个子实体可以在相同的级别上有多个始祖,但必须确认它只能在一个始祖的上下文中装载。

数据更改的操作按以下的规则运行。

  • 在递归执行任何子实体上的数据修改操作之前,更新、插入、或删除首先在父实体上执行。
  • 如果需要的数据操作没有出现在子实体中,那递归执行会停止。
  • 当更新一个父实体时,没有指定子实体数据操作的执行顺序。

下面的示例展示了SalesOrderHeader实体的查询、更改、删除方法。这些方法包含了在子实体内的处理改变的逻辑。



[EnableClientAccess()]
public class OrderDomainService : LinqToEntitiesDomainService
{

public IQueryable GetSalesOrders()
{
return this.ObjectContext.SalesOrderHeaders.Include("SalesOrderDetails");
}

public void UpdateSalesOrder(SalesOrderHeader currentSalesOrderHeader)
{
SalesOrderHeader originalOrder = this.ChangeSet.GetOriginal(currentSalesOrderHeader);

if ((currentSalesOrderHeader.EntityState == EntityState.Detached))
{
if (originalOrder != null)
{
this.ObjectContext.AttachAsModified(currentSalesOrderHeader, this.ChangeSet.GetOriginal(currentSalesOrderHeader));
}
else
{
this.ObjectContext.Attach(currentSalesOrderHeader);
}
}

foreach (SalesOrderDetail detail in this.ChangeSet.GetAssociatedChanges(currentSalesOrderHeader, o => o.SalesOrderDetails))
{
ChangeOperation op = this.ChangeSet.GetChangeOperation(detail);
switch (op)
{
case ChangeOperation.Insert:
if ((detail.EntityState != EntityState.Added))
{
if ((detail.EntityState != EntityState.Detached))
{
this.ObjectContext.ObjectStateManager.ChangeObjectState(detail, EntityState.Added);
}
else
{
this.ObjectContext.AddToSalesOrderDetails(detail);
}
}
break;
case ChangeOperation.Update:
this.ObjectContext.AttachAsModified(detail, this.ChangeSet.GetOriginal(detail));
break;
case ChangeOperation.Delete:
if (detail.EntityState == EntityState.Detached)
{
this.ObjectContext.Attach(detail);
}
this.ObjectContext.DeleteObject(detail);
break;
case ChangeOperation.None:
break;
default:
break;
}
}
}

public void DeleteSalesOrder(SalesOrderHeader salesOrderHeader)
{
if ((salesOrderHeader.EntityState == EntityState.Detached))
{
this.ObjectContext.Attach(salesOrderHeader);
}

switch (salesOrderHeader.Status)
{
case 1: // in process
this.ObjectContext.DeleteObject(salesOrderHeader);
break;
case 2: // approved
case 3: // backordered
case 4: // rejected
salesOrderHeader.Status = 6;
break;
case 5: // shipped
throw new ValidationException("The order has been shipped and cannot be deleted.");
default:
break;
}

}
}

2010年1月21日星期四

WCF RIA 服务 (十四)- 数据 4

如何自定义解决数据并发性冲突
WCF RIA Services框架把原值和更改后的值一起传递给数据访问层。这就允许数据访问层在提交数据更改之前检测数据并发性冲突。数据访问层是通过检测数据源中的当前值是否与Domain操作中检索到的值一致来判断冲突的。
我们可以为一个特定的实体自定义如何解决数据并发性冲突,这个方法在冲突确认时被调用。在这个方法中,可以指定步骤来解决冲突。这个方法必须匹配作为解决冲突的方法所需的签名。格式应如下:

  • 返回一个Boolean值。
  • 有个Resolve名字前缀或有ResolveAttribute。
  • 接受四个参数(current entity, original entity, store entity, 和一个指定是否本操作是个删除操作的值)

自定义解决数据并发性冲突

  1. 在domain service中,添加一个匹配做为解决方法所需签名的方法。
  2. 在这个方法中添加解决冲突的代码。下面的代码示例了一个名为ResolveProduct的方法,这个方法在冲突发生时会从domain操作中合并数据。 这个自定义的冲突解决代码,通过使用ResolveOption枚举值来指定合并数据。

    public bool ResolveProduct(Product currentProduct, Product originalProduct, Product storeProduct, Boolean deleteOperation)
    {
    return base.Resolve(currentProduct, originalProduct, storeProduct, ResolveOption.MergeIntoCurrent);
    }


如何向Domain service添加显式事务
可以在Domain Service中添加显式事务,来执行一组变化。
创建一个显式事务
  1. 在项目中,添加对System.Transactions程序集的引用。也可以从[program files]\Reference Assemblies\Microsoft\Framework\.NETFramework\[version]\System.Transactions.dll 找打。
  2. 在Domain Service中,重载Submit方法。
  3. 创建一个TransactionScope,它打包了对基类中Submit方法的调用。
  4. 在完成事务之前,检查ChangeSet对象的HasError成员属性。示例如下:

    [EnableClientAccess()]
    public class DomainService1 : DomainService
    {
    public override bool Submit(ChangeSet changeSet)
    {
    bool result = false;

    using (var tx = new TransactionScope(
    TransactionScopeOption.Required,
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted })
    )
    {
    result = base.Submit(changeSet);
    if (!this.ChangeSet.HasError)
    {
    tx.Complete();
    }
    }

    return result;
    }
    }

WCF RIA 服务 (十三)- 数据 3

如何验证数据
我们对实体和成员属性添加验证属性来实施验证规则。WCF RIA Service提供了几个验证属性来执行常用的验证检测,还提供了CustomValidationAttribute属性来执行自定义的验证检测。
在RIA Service中包含了如下的验证属性:

  • DataTypeAttribute
  • RangeAttribute
  • RegularExpressionAttribute
  • RequiredAttribute
  • StringLengthAttribute

我们在服务端添加验证属性,这些属性会传递给生成的客户端实体。在运行时,这些验证规则会应用到客户输入的数据上。我们必须通过添加元数据类来添加这些验证属性。

添加一个验证属性

  1. 为实体类添加一个元数据类,可以参考上一节内容。
  2. 对想要验证的实体或成员属性,添加验证属性来执行验证。下面的例子示例如何对一个名字为Address1的成员属性添加验证。

    [Required]
    [StringLength(60)]
    public string AddressLine1;

  3. 生成解决方案。
  4. 在silverlight的应用项目中,打开Generated_Code文件下的代码文件,注意到在客户端代码中也应用了验证属性。

添加自定义验证属性

  1. 对实体类添加一个元数据类。
  2. 添加一个共享代码文件,以*.shared.cs命名。这个文件将会包含一个自定义验证对象。
  3. 添加一个方法来判断是否数据有效。这个方法必须是public和static,还必须返回一个ValidationResult来表示验证的结果。下面示例是一个有名为IsProductValid方法的ProductValidator类,这个方法验证一个Product实体。

    public class ProductValidator
    {
    public static ValidationResult IsProductValid(Product productToValidate, ValidationContext context)
    {
    if (productToValidate.ListPrice < ((decimal).8 * productToValidate.StandardCost))
    {
    return new ValidationResult("ListPrice is below 80 percent of StandardCost.");
    }
    else
    {
    return ValidationResult.Success;
    }
    }
    }

  4. 对象要验证的实体或成员属性,添加[CustomValidationAttribute]批注属性,并传递验证对象和执行验证的方法的名字。下面的示例显示了对一个实体应用[CustomValidation]属性,验证对象的类型是 ProductValidator,验证方法的名字是 IsProductValid。

    [CustomValidation(typeof(RIAServicesExample.Web.SharedCode.ProductValidator), "IsProductValid")]
    [MetadataTypeAttribute(typeof(Product.ProductMetadata))]
    public partial class Product
    {

    internal sealed class ProductMetadata
    {

    // Metadata classes are not meant to be instantiated.
    private ProductMetadata()
    {
    }

    public string Color;

    public Nullable DiscontinuedDate;

    public decimal ListPrice;

    public DateTime ModifiedDate;

    public string Name;

    public Nullable ProductCategoryID;

    public int ProductID;

    public Nullable ProductModelID;

    public string ProductNumber;

    public Guid rowguid;

    public Nullable SellEndDate;

    public DateTime SellStartDate;

    [Required()]
    [StringLength(20)]
    public string Size;

    public decimal StandardCost;

    public byte[] ThumbNailPhoto;

    public string ThumbnailPhotoFileName;

    public Nullable Weight;
    }
    }

  5. 生成解决方案。
  6. 在Silverlight客户端,打开Generated_Code文件夹下的文件。注意到在共享代码中CustomValidationAttribute应用到了实体上。

2010年1月20日星期三

WCF RIA 服务 (十二)- 数据 2

如何添加元数据类


WCF RIA Services支持对实体类和成员的批注。批注是通过叫做元数据类的局部类来实施的。当我们想批注一个生成的实体类,并在重新生成这个实体类时不想丢失这些批注,我们使用元数据类。通过使用[MetadataTypeAttribute]属性来指定一个元数据类。

有两种方式来添加元数据类。一种是当添加一个新的Domain Service类时,指定自动生成元数据类。还可以手动添加元数据类。下面就介绍这两种方式。

通过使用Domain Service Class模板来添加元数据类


  1. 在服务端项目中,添加一个新建项并选择Domain Service Class模板。

  2. 当出现Add New Domain Service Class对话框时,选择想要公开的实体。

  3. 勾选Generate associated classes for metadata选框。

  4. 点击OK。就会生成一个Domain service class的文件和一个元数据文件。在元数据类的名字中会包含.metadata,例如:DomainService1.metadata.cs

  5. 打开元数据类并添加一些属性如下代码所示:

    [MetadataTypeAttribute(typeof(Address.AddressMetadata))]
    public partial class Address
    {

    internal sealed class AddressMetadata
    {
    // Metadata classes are not meant to be instantiated.
    private AddressMetadata()
    {
    }

    public int AddressID;

    [Required]
    [StringLength(60)]
    public string AddressLine1;

    public string AddressLine2;

    [Required]
    [StringLength(30)]
    public string City;

    public string CountryRegion;

    public EntityCollection CustomerAddresses;

    public DateTime ModifiedDate;

    [Required]
    public string PostalCode;

    [Exclude]
    public Guid rowguid;

    public string StateProvince;
    }
    }


手动添加元数据

  1. 在服务端项目中,添加一个新类。这个类的名字应与想要公开的实体类的名字一致。按照惯例,在名字用包含.metadata。

  2. 添加关键字partial来使类成为局部类。下面的代码示例了一个匹配名字为Address实体类的局部类。

    C# Copy Code
    public partial class Address
    {
    }


  3. 在局部类中,创建一个internal类作为元数据类。如下:

    public partial class Address
    {
    internal sealed class AddressMetadata
    {
    }
    }


  4. 对局部类添加[MetadataTypeAttribute属性,并包含元数据类的类型。代码如下:

    [MetadataTypeAttribute(typeof(Address.AddressMetadata))]
    public partial class Address
    {
    internal sealed class AddressMetadata
    {
    }
    }


  5. 在元数据类中,添加和实体类中成员属性同名的成员属性。

  6. 对成员属性添加属性批注。示例代码如下:

    [MetadataTypeAttribute(typeof(Address.AddressMetadata))]
    public partial class Address
    {

    internal sealed class AddressMetadata
    {
    // Metadata classes are not meant to be instantiated.
    private AddressMetadata()
    {
    }

    public int AddressID;

    [Required]
    [StringLength(60)]
    public string AddressLine1;

    public string AddressLine2;

    [Required]
    [StringLength(30)]
    public string City;

    public string CountryRegion;

    public EntityCollection CustomerAddresses;

    public DateTime ModifiedDate;

    [Required]
    public string PostalCode;

    [Exclude]
    public Guid rowguid;

    public string StateProvince;
    }
    }

WCF RIA 服务 (十一)- 数据 1

当我们在RIA客户端提供更新、删除、创建新数据等接口时,经常需要在提交更改之前确定用户输入的数据是有效的并且对数据源来说是最新的。WCF RIA Services提供了几个特性,来允许我们检查数据的有效性以及确定数据与客户端代码的并发性。还提供像使用单一数据实体那样与复杂的数据关系交互的特性。
数据批注
当使用数据类时,可以通过对类或方法应用属性,来指定验证规则、如何显示数据、以及设定实体类之间的关系。System.ComponentModel.DataAnnotations命名空间包含用做数据属性的类。通过对数据类或成员应用这些属性,您可以集中处理数据定义,而不必在多个位置重新应用相同的规则。。
当与自动生成的数据类一起工作时,例如一个实体数据模型或LINQ to SQL 类,不必对生成的类直接应用属性。应为这些属性在下次类重新生成时会丢失。相反,应对数据类创建一个元数据类并把属性应用在元数据类上。一个元数据类是一个部分类(Partial class),在数据类中做为元数据类型来指定的。
数据批注属性分为三种类型:验证,显示,数据模型。
表示层模型
可以为表示层建立一些类型,这些类型没有直接与数据源表的结构连在一起。例如,我们可以建立一个命名为CustomerPeresentation的数据类型,这个数据类型是以数据类Customer、CustomerAddress、Address表为基础的。在表示层中,只出现与表示层有关的值。如果是在数据存贮器中做的改变,那只能改变表示层中的类型,并不能更改客户端与数据交互的代码。RIA Services允许我们通过表示层类型来更新数据。
复合层
RIA Services支持与具有层式结构的数据模型交互。这个层式结构能反映出父亲与孩子那样的复杂关系,例如订单与订单详情。或者递归的关系,例如一个职工模型,其中包含一个叫做ManagerID的字段,这个字段指向职工模型中的其他实体。
继承
当使用VS2010和silverlight 4时,RIA Services支持多态继承模式。例如:你可以创建一个数据结构,它包含一个Customer实体和两个从它派生的实体:PublicSectorCustomer和PrivateSectorCustomer。使用Domain operations,可以查询和更新这些类型。
数据并发机制
当我们允许用户更新或删除数据时,会想在操作前确认这个数据没有被其他用户修改过。如果不检查这个数据是否被修改过,那会在无意中覆盖了其他人修改的数据,使数据处于矛盾的状态。
RIA Services会存储原数据与修改过的数据,然后把他们都传递给数据访问层。数据访问层竟会比较原数据与数据源中现有的数据。如果相同,就表示数据没有被改动过,那么数据层就更新或删除数据。如果数据已经改动过,那么就会把这个冲突存放在已经不再有当前数据实体的Conflict()属性内。我们也可以在发生数据冲突时,调用自定义的解决冲突的方法。
如果我们对实体内的某个成员应用了[Exclude]属性,这个实体是可以在客户端更新和删除的,不要使用这个成员来进行并发检测。当排除了这个成员,就不会对这个成员存贮正确的原数据,并且更新操作将会失败。
事务
RIA Services框架不会自动创建事务。但可以在提交改变时添加明确的事务。可以通过重载Submit方法来创建自己的事务。

WCF RIA 服务 (十)- Domain Services 3

如何在Domain Services中添加商业逻辑
即使在Domain Services中已经包含了更新、插入、删除等操作,但我们还是经常需要添加一些商业逻辑来管理那些修改数据的过程。还可能需要添加一些有别于传统的新的查询、更新等操作。这节中,我们将学习如何修改数据操作来满足商业要求,还学习如何添加一个命名的更新named update方法和一个调用invoke的操作
在操作数据的方法中添加商业逻辑


  1. 创建更新、插入、删除等应用程序所需的方法 :当在添加新的域服务类的对话框中生成域服务时,选择允许编辑(Enable editing)选项。或添加满足这些操作所需签名的方法。
  2. 在这些方法中,添加代码来指定处理需求的逻辑。
  3. 添加其他满足商业需求的方法。如果不想方法作为服务而公开,可以标记[IgnoreOperationAttribute]属性。

下面是一个插入的方法,此方法指派一个销售员。如果在公司的数据库中有顾客,RetrieveSalesPersonForCompany方法从公司中检索销售员的名字。这个方法标记了IgnoreOperationAttribute属性,所以客户端不能调用这个方法。


public void InsertCustomer(Customer customer
{
if (customer.SalesPerson == String.Empty)
{
customer.SalesPerson = RetrieveSalesPersonForCompany(customer.CompanyName);
}
this.ObjectContext.AddToCustomers(customer);
}

[IgnoreOperation]
public string RetrieveSalesPersonForCompany(string companyname)
{

string salesPersonToAssign = "unassigned";

List customers = GetCustomers().Where(c => c.CompanyName == companyname).ToList();
if (customers.Count > 0)
{
salesPersonToAssign = customers.First().SalesPerson;
}

return salesPersonToAssign;
}

添加命名的更新方法 named update method


  • 在Domain Services中,添加一个满足命名更新方法所需签名的方法。这个方法或者标记[Update]属性并设置UsingCustomMethod为true,或者接受一个实体作为第一个参数并没有返回值。下面的代码允许角色为CustomerRepresentative的用户重新设置客户的密码。

    [RequiresRole("CustomerRepresentative")]
    public void ResetPassword(Customer customer)
    {
    // Implement logic to reset password
    }

当添加一个命名的更新named update方法,在客户端会生成两个方法。一个在Domain context上生成,另一个是由实体生成的,这个实体是这个named update方法的传递参数。从客户端可以使用这两种方式来调用named update方法。调用这个方法后,还需要掉调用SubmitChanges方法。


selectedCustomer.ResetPassword();
customerContext.SubmitChanges(OnSubmitCompleted, null);




添加一个可调用的操作
在Domain Services类中,添加一个标记[Invok]属性的方法。
下面的示例表示如何根据邮编来查询当地的气温。


[Invoke]
public int GetLocalTemperature(string postalcode)
{
// Implement logic to look up temperature
}

然后可以通过使用InvokeOperation(TValue)对象来调用这个方法。如下所示:


InvokeOperation invokeOp = customerContext.GetLocalTemperature(selectedPostalCode);

如何使用HTTPS与Domain Services
当我们想改善建立在Domain Services上的通信安全时,可以配置Domain Services只接受https上的请求。此时Domain Services将拒绝所有http上的请求。当Domain Services配置为应用于https时,相应的DomainContext类也对所有的请求使用https。
想使用https,我们需要配置下Web服务器。可以通过How to Set Up an HTTPS Service in IIS和Configuring HTTP and HTTPS获得详细信息。配置完服务器后,还需要下面的步骤来指定Domain Services使用HTTPS。
当应用EnableClientAccessAttribute属性时,设置REquiresSecureEndpoint为true,代码如下

[EnableClientAccess(RequiresSecureEndpoint = true)]
public class AuthenticationDomainService : AuthenticationBase

2010年1月19日星期二

WCF RIA 服务 (九)- Domain Service 2

演练:添加查询方法
查询数据源的方法有时被叫做查询方法。在WCF RIA Services中,查询方法必须以框架承认的方式来定义。此外,只返回一个实体的查询和有可能返回多个实体的查询定义是不同的。
当我们建立一个新的domain service类并在Add New Domain Service Class 对话框中指定实体时,RIA Services框架会自动为每一个服务端公开的实体创建一个简单的查询。这个简单的查询方法检索实体的所有数据。这个演练将描述如何添加一个用参数值来过滤结果的复杂查询方法。还描述了如何添加一个返回单个实体和一个实体集合的查询。
添加一个接受参数并返回单一实体的查询方法

  1. 打开我们第三节中创建的RIAServicesExample解决方案。

  2. 在服务端,打开从Customer表公开数据的domain Services 类。这个类应该叫做CustmerDomainService。

  3. 添加一个查询方法,这个方法接受一个整数类型的参数并返回符合Customer ID的Customer实体。 如果返回单一实体的方法包含Query属性,必须设置IsComposable为false. 用户不能从客户端指定其他的查询操作。如果这个查询方法满足了作为查询所期望的签名,我们就不必使用[Query]属性。返回值必须是任何实体对象的单一实例。

    [Query(IsComposable=false)]



    public Customer GetCustomersByID(int customerID)



    {



    return this.ObjectContext.Customers.FirstOrDefault(c => c.CustomerID == customerID);



    }







添加一个接受一个参数并返回一个实体集合的查询方法

  1. 打开从Customer表公开数据的domain service类。名字应为CustomerDomainService。

  2. 添加一个方法,这个方法接受一个字符型参数并返回所有名字以参数开始的客户。这个方法可以返回一个IQueryable<>对象,因为用户可能想从客户端提供额外的查询。

    public IQueryable GetCustomersByLastNameLetter(string startingLastNameLetter)



    {



    return this.ObjectContext.Customers.Where(c => c.LastName.StartsWith(startingLastNameLetter) == true);



    }




在客户端显示这些查询的结果

  1. 在客户端打开MainPage.xaml文件。

  2. 添加两个TextBox控件和两个Button控件,这样用过就可以通过ID或名的首字母来过滤。下面的xaml代码显示了DataGrid的完整布局。








































































































  3. 打开MainPage.xaml的代码文件。


  4. 添加代码来根据用户的输入来检索数据。

    public partial class MainPage : UserControl



    {



    private CustomerDomainContext _customerContext = new CustomerDomainContext();







    public MainPage()



    {



    InitializeComponent();



    }







    private void LetterButton_Click(object sender, RoutedEventArgs e)



    {



    IDButton.IsEnabled = false;



    LetterButton.IsEnabled = false;



    LoadOperation loadOp = this._customerContext.Load(this._customerContext.GetCustomersByLastNameLetterQuery(LetterValue.Text), CustomerLoadedCallback, null);



    CustomerGrid.ItemsSource = loadOp.Entities;



    }







    private void IDButton_Click(object sender, RoutedEventArgs e)



    {



    IDButton.IsEnabled = false;



    LetterButton.IsEnabled = false;



    LoadOperation loadOp = this._customerContext.Load(this._customerContext.GetCustomersByIDQuery(int.Parse(IDValue.Text)), CustomerLoadedCallback, null);



    CustomerGrid.ItemsSource = loadOp.Entities;



    }







    void CustomerLoadedCallback(LoadOperation loadOperation)



    {



    IDButton.IsEnabled = true;



    LetterButton.IsEnabled = true;



    }







    }







  5. 运行解决方案。将会看到如下结果

WCF RIA 服务 (八)-- domain services 1

domain Services 是向客户端公开数据访问层的WCF Services。当我们创建一个domain services实例时,就指定了想要公开的实体类,以及这个domain Services所允许的数据操作。
DomainService类 和派生类
DomainService类是所有做为domain Services的服务类的基类。WCF RIA Services还提供了LinqToEntitiesDomainService(TContext)和LinqToSqlDomainService(TContext)类,这两个类是从DomainService类派生出来的抽象类。
如果想创建绑定一个ADO.NET实体模型或一个LINQ to SQL类,我们需要分别创建从LinqToEntitiesDomainService(TContext)或LinqToSqlDomainService(TContext)派生的类。如果创建绑定到自定义数据对象的domain Service,就需要创建一个从DomainService类派生的类。当我们使用Add New Domain Service Class对话框来创建domain service时,会根据想要公开的实体自动创建正确的domain service类型。
一个domain service必须标记EnableClientAccessAttribute属性,才能被客户端项目引用。
通过在domain service中添加方法来执行我们想公开的数据操作。例如,可以添加方法来执行以下操作:
  • 查询
  • 更新
  • 插入
  • 删除

还可以添加更复杂的操作,如下:

  • 处理操作 Resolve - 在服务端通过代码来处理发生的冲突。
  • 调用操作 Invoke - 那些不用追踪或延期运行的操作。
  • 命名的更新操作 Named Update - 不是简单修改的自定义操作。

命名约定

当我们添加方法来执行这些操作时,这些方法必须满足这些操作需要的签名。除了满足签名外,方法还应包含一个满足这个操作命名约定的名字前缀。如果方法的名字不是由预期的名字前缀开始的,就必须对这个操作应用一致的属性。如果操作的名字满足命名约定,那么属性就是可选的。

我们不能重载那些是域操作的方法。我们必须对每一个可以从客户端调用的方法指定唯一的名字。所有表示domain service操作的方法都应该是public的。方法对参数和返回值应使用串行类型。

可以通过对一个已经公开的方法使用IgnoreOperationAttribute属性,来阻止这个方法的调用。

下面的表提供了数据操作的签名:

Query

Return value : Itenumerable<T>,IQueryable<T>,ot entity

Parameters : Any number

Name Prefix : Any name

Attribute : [Query] (c#) or <Query()>(VB)

Example : public IQueryable<Product> GetProducts()

Update

Return value : None

Parameters : Entity

Name Prefix : Update, Change, or Modify

Attribute : [Update]

Example : public void UpdateProduct(Product product)

Insert

Return value : None

Parameters : Entity

Name Prefix : Insert, Add, or Create

Attribute : [Insert]

Example : public void InsertProduct(Product product)

Delete

Return value : None

Parameters : Entity

Name Prefix : Delete or Remove

Attribute : [Delete]

Example : public void DeleteProduct(Product product)

Resolve

Return value : Boolean

Parameters : Current entity, Original entity, Store entity, Boolean

Name Prefix : Resolve

Attribute : [Resolve]

Example : public bool ResolveProduct( Product currentProduct, Product orininalProduct, Product storeProduct, bool deleteOperation)

Invoke

Return value : any

Parameters : any

Name Prefix : any

Attribute : [Invoke]

Example :

[Invoke]

public decimal GetCompetitorsPrice(Product product)

Named Update

Return value : None

Parameters : entity, any number of other parameters

Name Prefix : 任何名字,只要和插入、更新、删除等操作的名字前缀不同即可。

Attribute : [Update(UsingCustomMethod=true]

Example :

[Update(UsingCustomMethod=true]

public void DiscontProduct(Product product, int percentage)

添加应用逻辑到Domain Service

在定义了要公开的数据操作之后,就可以把所需的应用逻辑添加到domain service类了。可以直接把逻辑添加到操作方法中,也可以添加到操作方法可调用的其他方法中。

WCF 和 Domain Services

domain service是建立在WCF概念上的。domain service保留了下面的特点:

  • WCF Service的标准使用方式。
  • 以有的WCF编程模型结构,例如操作合约,操作行为,以及服务行为。
  • 标准的WCF定制功能,例如绑定配置、行为配置等

domain context和domain service的交流是通过使用WCF ChannelFactory创建一个通道,并向它传递一个从domain service生成的服务合约。

2010年1月18日星期一

WCF RIA 服务 (七)- 中间层 简介

在3-层结构的应用程序中,中间层包含了如何管理在表示层和数据层之间交互的逻辑。我们在中间层应用商业逻辑和验证来确定数据是可接受的。例如,在人力资源应用程序中,我们可以提供一个允许员工提交休假申请的界面,但我们应该确定员工的可休假额度不小于0。因此,我们在提交申请之前,在中间层添加逻辑来检测员工的休假额度。
为了创建最好的RIA客户端用户体验,例如Silverlight应用,我们经常想在客户端执行也可在服务端执行的同样的商业逻辑。因此,中间层的代码在客户端和服务端同步就变得非常重要。WCF RIA Services使我们可以使用.NET Framework在中间层写应用逻辑。RIA Services从中间层为表示层生成代码,所以这些层总是同步的。

同数据访问层一起工作

当我们使用RIA Services来开发中间层时,可以使用任何种类的数据访问层。例如,我们可以连接下面的数据访问层:

  • Entity Data Model
  • LINQ to SQL object model.
  • Common language runtime (CLR) object.
  • Web service that exposes data from its source.

我们可以对数据模型应用验证检测以对来自表示层的数据值执行限制。

在有些情况下,我们不得不与那些不仅存在于一个表中的数据进行交互。RIA Services提供了一个可编程的框架,它支持更改级层数据模型(例如订单和订单详情的关系),继承数据模型(例如父和孩子关系),数据映射模型(例如从客户和地址表中提取数据放到一个单独的数据模型中的非正规数据)。

Domian Services

RIA Services提供DomainService类,这个类是所有做为数据层的数据接口类的基类。当创建一个domain Service的实例时,就指定了这个想从客户端访问的实体类。还可以通过domain service指定允许的数据操作,也可以向domain service中添加应用逻辑。对每一个向客户端开放的domain service,RIA services都将会为客户端应用程序生成一个DomainContext类。

Shared Code

在某些情况下,我们不得不在中间层项目和表示层项目中共享代码,但又不想把他们放入domain services 或 一个实体类。 例如,可能不得不引用一个包含应用逻辑并被许多其他项目使用的类库。或者,不得不创建一个从实体类连接值或改变值的自定义属性,例如以"last name, first letter of first name"的格式来显示名字。RIA Services允许我们在中间层包含类或程序集,这些类或程序集在客户端无需改动就可以访问。

生成客户端代码

当使用RIA Services来连接中间层和表示层时,RIA Services框架自动从中间层为客户端生成代码。以下中间层代码会生成客户端代码:

  • 每个注释着EnableClientAccessAttribute属性的domain service。
  • 每个domain service引用的实体类。
  • 由共享命名约定命名的代码文件(*.shared.cs或*.shared.vb)都会原样地拷贝到客户端项目中。

通过客户端代理类,客户端也可以访问中间层代码。当中间层的代码改变时,客户端的代理类会自动重生成。所以表示层总是与中间层同步。

WCF RIA 服务 (六)- 创建RIA Services 类库

RIA Services 类库允许我们创建能够重复使用的中间层和表现层逻辑。然而,使用RIA Services类库要比创建RIA Services解决方案复杂的多。
在本节演练中,将创建一个拥有RIA Services类库代码的SL应用程序。简单起见,把类库放在了SL应用程序相同的解决方案里。当然,类库也可以放在分开的解决方案中。

创建包含WCF RIA Services类库的SL解决方案


  • 在VS中,创建一个命名为ExampleSilverlightApp的新SL应用程序。


  • 新Silverlight应用程序 对话框中,不要勾选 WCF RIA Services 选项。这个应用程序不需要SL客户端和服务端之间的RIA Services link,因为这个RIA Services link将放在类库中。


  • 在资源管理器中,右键点击解决方案,选择 添加->新建项目。 出现添加新项目对话框。


  • 在Silverlight类型中,选择WCF RIA Services Class Library模板并命名为AdvertureWorksClassLibrary。


  • 点击OK。在解决方案中将包含四个项目,如下所示:




  • 右键点击 ExampleSilverlightApp.Web 项目,并选择 添加引用。 添加引用对话框出现。


  • 项目 标签中,选择 AdventureWorksClassLibrary.Web 项目,点击 OK。

  • 右键点击 ExampleSilverlightApp 项目,选择 添加引用

  • 项目 标签中, 选择 AdventureWorksClassLibrary 项目,点击 OK。

创建中间层库



  1. 在 AdventureWorksClassLibrary.Web项目中,添加一个名为 AdventureWorksModel.edmx的 ADO.NET Entity Data Model。

  2. 在实体数据模型向导中,把 Product 表加到实体模型中。

  3. 生成解决方案。

  4. 右键点击 AdventureWorksClassLibrary.Web项目,选择 添加->新项

  5. 选择 Domain Service Class 模板,并命名为ProductsDomainService。

  6. 点击 添加。 出现 添加新域服务类 对话框。

  7. 从domain service中提供的数据模型中选择 Product, 并点击 OK

  8. 生成解决方案。

  9. 在解决方案中,对每个项目选择 显示所有文件-Show All Files。我们可以发现仅在AdventureWorksClassLibrary项目中存在Generated_Code文件夹。虽然没有为ExampleSilverlightApp项目生成代码,但我们仍可以使用在AdventureWorksClassLibrary项目中生成的代码。因为在ExampleSilverlightApp项目和AdventureWorksClassLibrary项目间存在项目引用。

在SL项目中使用生成的代码



  1. 右键点击ExampleSilverlightApp项目,选择 添加引用

  2. 添加对 System.Windows.Ria 程序集的引用。通过导航到[Program Files]\Microsoft SDKs\RIA Services\v1.0\Livryries\Silverlight, 可以找到这个程序集。

  3. 在ExampleSilverlightApp项目中,打开MainPage.xaml文件。

  4. 从工具箱中,拖拽DataGrid控件到Grid内。 这会自动添加一个XML命名空间和一个数据程序集引用。

  5. 命名DataGrid为 ProductsGrid, 如下所示:















  6. 打开MainPage.xaml的后台代码。

  7. 添加下面的代码来检索产品。

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Net;

    using System.Windows;

    using System.Windows.Controls;

    using System.Windows.Documents;

    using System.Windows.Input;

    using System.Windows.Media;

    using System.Windows.Media.Animation;

    using System.Windows.Shapes;

    using AdventureWorksClassLibrary.Web;

    using System.Windows.Ria;



    namespace ExampleSilverlightApp

    {

    public partial class MainPage : UserControl

    {

    private ProductDomainContext _productContext = new ProductDomainContext();



    public MainPage()

    {

    InitializeComponent();



    LoadOperation loadOp = this._productContext.Load(this._productContext.GetProductsQuery());

    ProductsGrid.ItemsSource = loadOp.Entities;

    }

    }

    }



  8. 打开AdventureWorksClassLibrary.Web项目中的App.Config文件,分别拷贝<connectionStrings>,<system.serviceModel>,和<httpModules>元素。把它们粘贴到ExampleSilverlightApp.Web项目的Web.config文件中。现在Web.config文件看上去和下面的代码类似,当然你应该提供和你的环境相关的链接信息。









































  9. 运行应用程序。看到如下结果。

2010年1月17日星期日

WCF RIA 服务 (五)- 使用SL商业应用模板

SL商业应用程序模板创建一个自动包含许多你所想要的特性的应用程序,例如用户登录和注册的控件。这个项目还建立SL导航,这就意味着你能够方便的添加新的SL页来添加新功能。

默认的情形下,SL商业应用程序模板允许身份认证、角色和Profiles。

在这个演练中,学习如何用SL商业应用程序模板来创建WCF RIA Services。
  • 在VS中,选择 文件->新建项目。出现 新建项目 对话框。
  • 项目类型 中选择 Silverlight.
  • 选择Silverlight Business Application模板,并命名为ExampleBusinessApplication.


  • 点击OK。注意创建的项目结构。在SL客户端中的Views文件夹中包含有SL页。这些页可以使用户注册和登录。
  • 在资源管理器中,右键点击客户端项目并选择属性。在SL标签的底部,注意到在客户端和服务端之间存在着RIA Services link。
  • 运行应用程序。在浏览器中显示应用程序的首页。

  • 点击 登录login 链接。登录对话框将出现。

  • 点击底部的 注册Regiser now 链接。 注册对话框将出现。

  • 关闭对话框。

WCF RIA 服务 (四)- 如何添加和移除RIA Services Link

当创建一个新WCF RIA Services解决方案时,你将会在客户端和服务端之间指定一个RIA Services Link。当然我们也可以在已存在的项目中添加和移除Link。

在已存在项目中添加RIA Services Link
  1. 在资源管理器中,在Silverlight客户端上右键点击,并选择属性

  2. 在 WCF RIA Services link 下拉单中,选择想用来当中间层的服务端项目。

从项目中移除RIA Services link


  1. 在资源管理器中,右键点击silverlight客户端应用程序并选择属性

  2. WCF RIA Services link 下拉菜单中,选择"No Project Set"

WCF RIA 服务 (三)- 创建一个RIA Services Solution

在本节,我们将进行一个演练。我们创建一个WCF RIA Services 应用,此应用从AdventureWorksLT 数据库中检索数据。我们会用SL来创建表现层的客户端,并创建表现不同数据库表的实体类。

此节需要:




  • Visual Studio 2008
  • Silverlight 3
  • WCF RIA Services Beta for visual Studio 2008 SP1
-or-

  • Visual Studio 2010 Beta 2

  • Silverlight 4 Beta

  • WCF RIA Services Preview for Visual Studio 2010

  • AdventureWorksLT sample database



创建包含RIA Services Link的解决方案

  1. 在VS中,创建一个新的RIA Services项目,选择 File->New->Project。此时出现新项目对话框。

  2. 选择Silverlight应用程序模板,并命名为RIAServicesExample.

  3. 点击OK。出现New Silverlight Application对话框出现。

  4. 选择Enable WCF RIA Services选项框。现在将会在客户端和服务端之间创建连接。

  5. 点击OK来创建解决方案。这个方案包含两个项目:客户端和服务端。客户端被命名为RIAServicesExample,其中有用来创建表示层的SL代码。服务端被命名为RIAServicesExample.Web,其中包含中间层的代码。



创建数据模型 Data Models

在这部分,创建表示来自AdventureWorksLT数据库的数据的ADO.NET实体类。RIA Services与各种各样的数据模型类和数据源一起工作。

(注:在VS2010中使用实体数据模型时,应该选择Include foreign key columns in the model选项。当用实体数据模型向导时,这个选项是默认选择的。还应该使用在程序集内嵌入的实体数据模型映射信息的默认行为。)

---使数据在中间层可利用

  1. 在解决方案资源管理器中,右键点击服务端项目(RIAServicesExample.Web),选择添加->新项目, 添加新项目对话框将出现。

  2. 在类别列表中,选择数据并选择ADO.NET Entity Data Model模板。

  3. 命名为AdventureWorksModel.edmx并点击添加。 出现实体数据模型向导。

  4. 选择模型内容 中,选择 从数据库生成 ,然后点击下一步

  5. 选择数据连接 中,创建到数据库的数据连接并点击下一步

  6. 选择数据库对象 里,选择 Address, Customer, CustomerAddress表。

  7. 点击 完成 ,就为表生成了实体模型。

  8. 生成解决方案
创建域服务Domain Service

在这节中,将添加一个对中间层的domain service。domain service 把服务端的数据实体和操作
向客户端公开。可以在数据服务中加入商业逻辑来管理客户端如何与数据交互。

---创建domain service


  1. 右键点击服务端项目,选择 添加->新项目

  2. 在列表中选择 Web, 然后选择 Domain Service Class 模型。

  3. 命名类为CustomerDomainService.cs(或 CustomerDomainService.vb)。

  4. 点击 添加。 出现 添加新域服务类 对话框。

  5. 选择自定义实体。

  6. 确定选了,Enable client access选项, 选择对应 自定的 Enable Editing选项。

  7. 点击 OK,就生成了domain service 类。

  8. 打开CustomerDomainServie.cs文件。会注意到文件有如下属性:


  • CustomerDomainService类派生于LinqToEntitiesDomainService(TContex),这个基类是RIA Services框架内的一个抽象类。由于domain Services公开了ADO.NET 实体数据类,此基类是自动应用的。

  • 一个泛型基类被绑定到上一步创建的实体类。

  • CustomerDomainService类被标记上EnableClientAccess属性,来指出这个类是可以被客户层访问的。

  • 生成了一个查询方法。在VS2008中,这个查询叫做GetCustomer。在VS2010Beta2中,这个查询方法叫做GetCustomers。这个方法返回没有过滤和排序的所有项。

  • 还生成了 InsertCustomer, UpdateCustomer, DeleteCustomer方法。
创建Silverlight客户端

在其他的演练中,将在domain service里加入商业逻辑。在本演练中,只是用生成的GetCustomers 方法。

由于在客户端和服务端存在着一个RIA Services Link,当生成解决方案时就会产生客户代理类。这个代理类允许我们从客户端访问数据。

--- 查看产生的客户端代理类


  1. 生成解决方案

  2. 在解决方案资源管理器中,在客户端项目中点击显示所有文件。注意到在Generated_Code文件夹中包含了一个代码文件。




3. 打开这个代码文件。注意到文件有下面的属性:

  • 生成一个派生于WebContextBase类的WebContext类。

  • 生成一个派生于DomainContext类的CustomerDomianContext类。这个类中有一个叫做GetCustomersQuery(在VS2008中是GetCustomerQuery)方法,它对应于在domain service中创建的查询方法。

  • 生成一个派生于Entity类的Customer类,此类对应着domain service公开的实体。这个客户端的Customer实体类对应于服务端的Customer实体。



在Silverlight客户端中显示数据




  1. 打开 MainPage.xaml。

  2. 从工具箱里,拖拽一个DataGrid控件到Grid里。当从工具箱中拖拽DataGrid控件时,会自动添加一个XML命名空间和一个对数据程序集的引用。如果你不是从工具箱中拖拽的DataGrid控件,你需要添加一个对System.Windows.Controls.Data程序集的引用和一个XML命名空间 xmlns:data="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"

  3. 命名这个控件为 CustomerGrid,如下面所示:











  4. 打开MainPage.xaml的后台代码文件。

  5. 添加RIAServiceExample.Web命名空间和System.Windows.Ria命名空间。RIAServices.Web命名空间位于客户端的生成代码里。

  6. 添加CustmerDomainContext实例,通过GetCustomerQuery方法来检索信息,并绑定到DataGrid控件,如下代码所示:

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using System.Net;

    using System.Windows;

    using System.Windows.Controls;

    using System.Windows.Documents;

    using System.Windows.Input;

    using System.Windows.Media;

    using System.Windows.Media.Animation;

    using System.Windows.Shapes;

    using RIAServicesExample.Web;

    using System.Windows.Ria;



    namespace RIAServicesExample

    {

    public partial class MainPage : UserControl

    {

    private CustomerDomainContext _customerContext = new CustomerDomainContext();



    public MainPage()

    {

    InitializeComponent();



    LoadOperation loadOp = this._customerContext.Load(this._customerContext.GetCustomersQuery());

    CustomerGrid.ItemsSource = loadOp.Entities;

    }

    }

    }

  7. 运行应用程序。将会得到一个如下类似的表格:

2010年1月15日星期五

WCF RIA 服务 (二)- 解决方案结构

上节大概介绍了一下 WCF RIA 服务, 这次介绍下整个解决方案的结构。

当创建应用程序时,WCF RIA服务可以帮你建立满足各种各样情况的解决方案。例如只是在中间层访问很少domain services的Silverlight应用程序。更复杂的例子可能是几个Silverlight程序都连接到一个提供许多domain services的通用中间层。本节就介绍几种构建RIA服务解决方案的方式。

RIA 服务连接

在所有的RIA Services方案中,一个连接(就是RIA服务连接)存在于中间层项目和表示层项目之间。一个RIA Services连接是一个项目对项目引用的特殊模式,它更便于从中间层的代码来生成表现层的代码。在创建解决方案时选择 Enable WCF RIA Services 选项,就可以建立RIA Services 连接了。你也可以在已有项目的属性中建立RIA Services link。有的时候建立的连接是在应用项目之间的,而有些时候是建立在类库项目之间的。

当在项目中存在link时,表示层将会收到中间层的所有代码。不能指定只有一部分的代码应用到表示层。下面的规则用于RIA Services Link:
  • 在Silverlight的客户端项目中定义link。
  • link总是由Silverlight客户端指向.NET 服务端或类库。
  • 一个Silverlight客户端只能有一个link。
  • 这个link不能指向其他Sliverlight客户端。
  • 多个Silverlight客户端可以指向同一个服务端或类库。
  • 一个Silverlight应用程序不能连接到一个类库项目。

默认的解决方案结构

在默认的解决方案结构中,RIA Services创建一个单一的客户端项目和一个服务端项目。当使用Silverlight Application模板并勾选 Enable WCF RIA Services来创建项目时,就建立了一个默认的结构。一个RIA Services Link就已经在两个项目中存在了。当你生成解决方案时,将生成相对Domain Services和共享代码的客户端代码。下面的图展示了默认结构:

这个默认的结构是很方便的,因为所有的domain services类型和共享代码都在生成解决方案后自动添加到服务端和客户端。而且添加在服务端的共享代码,在客户端也是可见的。当你没有很多的domain services在服务端并且你也不必在很多不同的SL应用程序中重用商业逻辑时,默认的结构就已经可以很好的工作了。

在默认的解决方案结构中,你可以给服务端项目添加更多的具有RIA Services Link的SL应用程序。然而,默认的结构也存在局限性。对每一个SL客户端生成的代码包含所有来自服务端的中间层代码。例如,如果你有3个连接到一个服务端的SL应用程序,并且你想添加一个只能被其中一个SL应用程序使用的domain service,那么这3个客户端应用程序都将拥有为这个domain service生成的domain上下文并且可以访问这个domain service.

Silverlight Business Application template

RIA Services还提供了一个 Silverlight Business Application 模板。这个模板对建立一个SL商业应用程序提供了一个方便的出发点。这个模板建立在SL导航应用程序上,并用RIA Services来支持身份认证和用户注册。当用SL商业应用程序模板创建项目时,RIA Services建立默认的结构,并自动添加下面的特性:

  • 登录窗口
  • 注册窗口
  • SL导航
支持N层类库组件(Class Library Components)

RIA Services提供WCF RIA Services Class Library 项目类型来支持库中共享代码。通过类库,你把商业逻辑打包在N层类库组件里。下图展示使用RIA Services类库的解决方案结构:





在上图中,注意RIA Services Links并没存在于应用程序之中。相反,存在于类库项目中。你可以在你的应用程序中使用任意多的类库,并可以在任意的应用程序中重用这些类库。


使用RIA Services 类库有如下好处:

  • 服务端和一个单一数据域的客户端可以被作为一个单一的组件来开发和打包。这个组件可以在多个应用程序用重用。

  • 客户端的代理代码生成和源码共享都发生在一个位置。这个位置在每个组件的层中而不在每个SL应用程序中。

  • 在一个单一的Web应用程序中的多个SL应用可以指向它们所需的那个类库。每个SL应用程序不用再不得不看到中间层所公开的商业逻辑。

用RIA Services 类库,你可以仅提供应用程序所需的组件来建立灵活的解决方案结构。下图展示了一个应用多个RIA Services类库的方案结构:

WCF RIA 服务 (一)简介

WCF RIA Services简化了N层结构的RIA应用程序的开发,例如Silverlight应用。当开发一个N层结构的RIA应用程序时,一个通常的问题就是在中间层和表现层之间协调应用逻辑。为了建立更好的用户体验,你可能想要你的RIA客户端了解在服务器上的应用逻辑,但并不想开发和维护在表现层和中间层上的应用逻辑。现在RIA服务可以解决这个问题,它提供框架组件、工具以及服务来使RIA客户端不用手动复制程序逻辑就能调用位于服务器上的应用逻辑。你所建立的RIA客户端不仅能够了解商业逻辑,而且当每次解决方案编译时会自动更新中间层逻辑。


下图显示了一个简单的N层应用程序。RIA服务集中于在表现层和数据访问层之间的盒子内,目的是使n层结构的RIA客户端开发更简单。















RIA服务在Visual Studio中添加工具,使用这些工具能够在一个解决方案中把客户端和服务器端的项目连接起来,并且从中间层的代码中为客户端的项目生成代码。这些组件支持编写应用逻辑的规范模式,所以它能够在表现层中重复使用。提供适于通用情况的服务可以减少开发时间,例如身份验证和用户设置等。
可以从RIA服务站点来下载WCF RIA Services,现在提供两个版本:
  • WCF RIA Services Beta for Visual Studio 2008 SP1
  • WCF RIA Services Preview for Visual Studio 2010

在RIA Services中,通过添加域服务(domain services)来向客户端项目公开服务器项目的数据。RIA服务框架引用的域服务就是调用WCF服务。因此,当自定义配置时,可以使用从WCF服务中得到的概念来应用到域服务中。

2010年1月14日星期四

Silverlight 导航概述 2

外部导航

应用程序可以提供指向其他网页的直接链接。外部导航可用于提供对应用程序外部资源的访问。还可以将外部导航用于 Silverlight 来实现控件(如用于普通网页的边栏菜单)。
在某些情况下,可能需要对应用程序禁止任何外部导航。若要禁用所有外部导航,在初始化 Silverlight 插件时,应将 enableNavigation 属性设置为 none。
若要启用对其他网页的用户导航,可以使用 HyperlinkButton 控件并将 NavigateUri 属性设置为外部资源,将 TargetName 属性设置为打开新的浏览器窗口。
下面的代码示例演示如何使用 HyperlinkButton 导航到应用程序外的某个位置。





需要以编程方式从某一页启动导航请求时,必须首先获取宿主框架使用的 NavigationService 对象。NavigationService 类提供用于页导航的成员,如 GoBack、GoForward 和 Source。
下面是我自己做的一个小例子,没用SDK带的那个。那个例子对如何调用页面没有介绍。


我在这个例子中没有用到数据库,只是用了一个XML文件作为数据源。为了更方便的传递所有信息,我建了一个Product类,其中包含名称、数量、价钱、图片地址这些公共属性。在读取完XML数据后,生成List<product>,然后再把它赋值给ListBox控件的ItemsSource属性。
在ListBox控件中,我使用DisplayMemberPath="Name"来只显示Product Name。
然后在MainPage.xaml文件中,添加地址映射如下,注意用"& amp;"(中间无空格)来取代&




此时,我们就可以通过编程方式来导航了。在ListBox控件的SelectionChanged事件中,我们通过NavigationService来实现页面导航,代码如下

private void ListBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
ListBox listbox = sender as ListBox;
Product product = (Product)listbox.SelectedItem;
NavigationService.Navigate(new Uri("/ProductDetail/" + product.Name+"/"+product.Price+"/"+product.Quantity+"/"+product.ImageUri, UriKind.RelativeOrAbsolute));
}

由于我们是把Product赋值给ListBox,所以通过Product product = (Product)listbox.SelectedItem,现在我们很容易就获得了所选产品的所有信息,而不仅仅是名字。然后把这些信息放到所想访问的地址中去,再通过NavigationService.Navigate来实现页面调用。
在ProductDetail页中,我们通过NavigationContext来获得传递的参数,代码如下

protected override void OnNavigatedTo(NavigationEventArgs e)
{
if (this.NavigationContext.QueryString.ContainsKey("ProductName"))
{
productName.Text = NavigationContext.QueryString["ProductName"];
productQuantity.Text = "Quantity : "+ NavigationContext.QueryString["ProductQuantity"];
... ...
}
}

至此,我们就可以像在ASP.NET中那样,在任意页面中导航了。

2010年1月11日星期一

Silverlight 导航概述 1

本文摘自Silverlight3 SDK 导航概述

应用程序导航

在 Silverlight 应用程序中使用 Frame 和 Page 控件可以实现应用程序导航。页面(Page)控件表示内容的独立部分。框架(Frame)用作页面控件的容器,并使页导航非常简便。在任一时刻,框架只显示一个页面的内容。以编程方式或通过用户操作导航到新页时,框架中显示的页将会更改。
可以将 Silverlight 应用程序的根视觉效果设计为包含可导航内容和永久用户界面 (UI) 组件(例如页眉、页脚和导航边栏)的组合。使用"Silverlight 导航应用程序"模板创建新项目时,该模板会生成一个包含永久 UI 组件的 XAML 文件并为可导航内容生成一个框架。
下面的示例演示一个简单框架,它存在于名为 MainPage 的 UserControl 控件内。您可以在 ContentFrame 之前或之后向 LayoutRoot 添加其他 UI 组件。Source 设置为 /Views/Home.xaml 表示默认情况下,框架中显示的页是位于 /Views/Home.xaml 的页。在实际创建的应用程序中,Source被简单设置为/Home

通过使用"添加新项"对话框,然后选择"Silverlight 页",可以将新页添加到应用程序中。Visual Studio 中的"Silverlight 导航应用程序"模板会创建一个名为 Views 的文件夹,其中包含这些页。您可以向此文件夹添加页,也可以在应用程序中最适当的任何位置添加页。

创建用户友好的 URI

在框架中,可以为特定页面指定某种 URI 模式映射。使用 URI 映射可以创建说明用户操作的 URI,而不是文件的路径。例如,可以指定对 /Home 的任何请求实际上是对位于 /Views/Home.xaml 的文件的请求。不与任何已定义模式匹配的所有请求都作为常规 URI 请求来处理,不会映射到其他页。下表演示 URI 映射定义的示例以及如何解析这些示例请求。

URI 映射定义: Uri = "/Home" MappedUri = "/Views/Home.xaml"
匹配 URI 示例 : /Home 解析后的 URI : /Views/Home.xaml
URI 映射定义: Uri = "/{page}" MappedUri = "/Views/{page}Page.xaml"
匹配 URI 示例 : /About 解析后的 URI : /Views/AboutPage.xaml
URI 映射定义: Uri = "/Product/{category}" MappedUri = "/ContosoShop/Product.xaml?category={category}"
匹配 URI 示例 : /Product/bikes 解析后的 URI : /ContosoShop/Product.xaml?category=bikes
URI 映射定义: Uri = "/{reporttype}/{month}/{format}"
(在 XAML 中)MappedUri = "/Views/Reports/{reporttype}.xaml?time={month}&show={format}"
(在 Visual Basic 或 C# 中)MappedUri = "/Views/Reports/{reporttype}.xaml?time={month}&show={format}"
匹配 URI 示例 : /Sales/June/Short
解析后的 URI : /Views/Reports/Sales.xaml?time=June&show=Short

向框架添加 URI 映射的方法是定义 UriMapper 类(或派生自 UriMapperBase 类的自定义类)的一个实例以及任意数量的 UriMapping 实例。您指定的模式不必是与所请求 URI 完全匹配的 URI。该模式可以在 URI 中包含占位符段,URI 将匹配该段中的任何值。将占位符段的名称括在大括号({ 和 })中可以指定占位符段。在映射 URI 时,占位符段充当变量。任何没有括在大括号中的值表示文本值,要与模式匹配的 URI 必须存在该值。下面的示例演示包含占位符值的 URI 模式。















URI 请求将映射为与该请求匹配的第一个模式。因此,应按照从最具体到最一般的顺序添加 URI 映射实例。例如,下面的定义是按照从具体文本值到一般占位符值正确排序的。对 /SalesReport 的请求将映射为 /Views/Reports/Sales.xaml















但是,如果颠倒 URI 映射定义的顺序,对 /SalesReport 的请求将不会映射为 /Views/Reports/Sales.xaml。相反,第一个定义 /{page} 将匹配每一个段请求,包括 /SalesReport。该请求将映射为 /Views/SalesReportPage.xaml。

方便页导航

Frame 类提供用于页导航的方法和属性。将 Source 属性设置为要显示的页的 URI,或调用 Navigate 方法并将该页的 URI 作为参数来传递。也可以使用 GoBack 和 GoForward 方法在导航历史记录中向前或向后导航。
可以使用 HyperlinkButton 控件使用户能够在应用程序的各页中导航。宿主框架将导航到请求的页。下面的示例演示一个包含了导航到应用程序中另一页的 HyperlinkButton 的页。








2010年1月7日星期四

Sushi Menu

在blend3中看到一个颜色板的例子,觉得可以用来展示菜单。
于是就做了个简单的寿司菜单例子。
这个例子中用到的主要元素是listbox控件。通过ItemsPanel属性调用自定义模板,使内容旋转排版。
这个自定义模板是通过一个继承Panel的类来实现的。在每次渲染(重载ArrangeOverride函数)时,遍历容器内的所有子控件,使它们旋转一定的角度。

2010年1月5日星期二

安装silverlight新版本出现问题

前两天有个朋友想看这个博客,可发现需要安装新版本的silverlight,于是就下载了安装软件,却发现安装不了。总是跳出需要silverlight.msi文件的提示。其实这是由于他安装过以前的版本,在安新版本之前,需要这个文件卸载掉老版本。
可以从微软的网站下下载个installer.exe,然后安装运行,在里面选择删除以前的silverlight版本。这样就可以安装新版本了。

2010年1月4日星期一

Music Player Version 2

光是播放音乐,显得功能太简单。所以又加上了歌词功能。把歌词和每句的时间放在了XML文件中,这样就可以在歌曲加载后,马上加载歌词。