WCF的所有服务都会公开为契约(Contract)。契约与平台无关,是描述服务功能的标准方式。WCF定义了四种类型的契约。
服务契约(Service Contract)
服务契约描述了客户端能够执行的服务操作。服务契约是下一章的主题内容,但书中的每一章都会广泛使用服务契约。
数据契约(Data Contract)
数据契约定义了与服务交互的数据类型。WCF为内建类型如int和string隐式地定义了契约;我们也可以非常便捷地将定制类型定义为数据契约。本书第3章专门介绍了数据契约的定义与使用,在后续章节中也会根据需要使用数据契约。
错误契约(Fault Contract)
错误契约定义了服务抛出的错误,以及服务处理错误和传递错误到客户端的方式。第6章专门介绍了错误契约的定义与使用。
消息契约(Message Contract)
消息契约允许服务直接与消息交互。消息契约可以是类型化的,也可以是非类型化的。如果系统要求互操作性,或者遵循已有消息格式,那么消息契约会非常有用。由于WCF开发者极少使用消息契约,因此本书不会介绍它。
服务契约
ServiceContractAttribute的定义如下:
[AttributeUsage(AttributeTargets.Interface|AttributeTargets.Class,
Inherited = false)]
public sealed class ServiceContractAttribute : Attribute
{
public string Name
{get;set;}
public string Namespace
{get;set;}
//更多成员
}
这个特性允许开发者定义一个服务契约。我们可以将该特性应用到接口或者类类型上,如例1-1所示。
例1-1:定义和实现服务契约
[ServiceContract]
interface IMyContract
{
[OperationContract]
string MyMethod(string text);
//不会成为契约的一部分
string MyOtherMethod(string text);
}
class MyService : IMyContract
{
public string MyMethod(string text)
{
return "Hello " + text;
}
public string MyOtherMethod(string text)
{
return "Cannot call this method over WCF";
}
}
ServiceContract特性可以将一个CLR接口(或者通过推断获得的接口,后面将详细介绍)映射为与技术无关的服务契约。ServiceContract特性公开了CLR接口(或者类)作为WCF契约。WCF契约与类型的访问限定无关,因为类型的访问限定属于CLR的概念。即使将ServiceContract特性应用在内部(Internal)接口上,该接口同样会公开为公有服务契约,以便于跨越服务边界实现服务的调用。如果接口没有标记ServiceContract特性,WCF客户端则无法访问它(即使接口是公有的)。这一特点遵循了面向服务的一个原则,即明确的服务边界。为满足这一原则,所有契约必须明确要求:只有接口(或者类)可以被标记为ServiceContract特性,从而被定义为WCF服务,其他类型都不允许。
即使应用了ServiceContract特性,类型的所有成员也不一定就是契约中的一部分。我们必须使用OperationContractAttribute特性显式地标明哪些方法需要暴露为WCF契约中的一部分。OperationContractAttribute的定义如下:
[AttributeUsage(AttributeTargets.Method)]
public sealed class OperationContractAttribute : Attribute
{
public string Name
{get;set;}
//更多成员
}
WCF只允许将OperationContract特性应用到方法上,而不允许应用到同样属于CLR概念的属性、索引器和事件上。WCF只能识别作为逻辑功能的操作(Operation)。通过应用OperationContract特性,可以将契约方法暴露为逻辑操作,使其成为服务契约的一部分。接口(或类)中的其他方法如果没有应用OperationContract特性,则与契约无关。这有利于确保明确的服务边界,为操作自身维护一个明确参与(Opt-In)的模型。此外,契约操作不能使用引用对象作为参数,只允许使用基本类型或数据契约。
应用ServiceContract特性
WCF允许将ServiceContract特性应用到接口或类上。当接口应用了Service-Contract特性后,需要定义类实现该接口。总的来讲,我们可以使用C#或VB去实现接口,服务类的代码无需修改,自然而然成为一个WCF服务:
[ServiceContract]
interface IMyContract
{
[OperationContract]
string MyMethod();
}
class MyService : IMyContract
{
public string MyMethod()
{
return "Hello WCF";
}
}
我们可以隐式或显式实现接口:
class MyService : IMyContract
{
string IMyContract.MyMethod()
{
return "Hello WCF";
}
}
一个单独的类通过继承和实现多个标记了ServiceContract特性的接口,可以支持多个契约。
[ServiceContract]
interface IMyContract
{
[OperationContract]
string MyMethod();
}
[ServiceContract]
interface IMyOtherContract
{
[OperationContract]
void MyOtherMethod();
}
class MyService : IMyContract,IMyOtherContract
{
public string MyMethod()
{...}
public void MyOtherMethod()
{...}
}
然而,服务类还有一些实现上的约束。我们要避免使用带参构造函数,因为WCF只能使用默认构造函数。同样,虽然类可以使用内部(internal)的属性、索引器以及静态成员,但WCF客户端却无法访问它们。
WCF允许我们直接将ServiceContract特性应用到服务类上,而不需要首先定义一个单独的契约:
//避免
[ServiceContract]
class MyService
{
[OperationContract]
string MyMethod()
{
return "Hello WCF";
}
}
通过服务类的定义,WCF能够推断出契约的定义。至于OperationContract特性,则可以应用到类的任何一个方法上,不管它是私有方法,还是公有方法。
警告:应尽量避免将ServiceContract特性直接应用到服务类上,而应该定义一个单独的契约,这有利于在不同场景下使用契约。
名称与命名空间
可以为契约定义命名空间。契约的命名空间具有与.NET编程相同的目的:确定契约的类型范围,以降低类型的冲突几率。可以使用ServiceContract类型的Namespace属性设置命名空间:
[ServiceContract(Namespace = "MyNamespace")]
interface IMyContract
{...}
若非特别指定,契约的默认命名空间为http://tempuri.org。对外服务的命名空间通常使用公司的URL;至于企业网(Intranet)内部服务的命名空间,则可以定义有意义的唯一名称,例如MyApplication。
在默认情况下,契约公开的名称就是接口名。但是也可以使用ServiceContract特性的Name属性为契约定义别名,从而在客户端的元数据(Metadata)中公开不同的名称:
[ServiceContract(Name = "IMyContract")]
interface IMyOtherContract
{...}
相似的,操作公开的名称默认为方法名,但我们同样可以使用OperationContract特性的Name属性设置别名,从而公开不同的操作名:
[ServiceContract]
interface IMyContract
{
[OperationContract(Name = "SomeOperation")]
void MyMethod(string text);
}