复习WCF(6)----- 消息协定
今天看到一句很有感觉的话:你能作茧自缚,就能破茧成蝶
消息样式的操作最多具有一个参数和一个返回值,其中参数和返回值的类型都是消息类型
也就是说,这两种类型可直接序列化为指定的soap消息结构
可以用MessageContractAttribute标记的任何类型或Message类型(Message类型下次说)
[OperationContract]
bool Validate(BankingTransaction bt); //错误 返回值是bool类型 他不是消息类型 所以他是无效的消息协定
[OperationContract]
void Reconcile(BankingTransaction bt, BankingTransaction bt2);//错误 参数只能最多为一个 所以这也是无效的消息协定
若要为某一个类型定义消息协定(即定义该类型和soap信封之间的映射)
请对该类型使用MessageContractAttribute属性修饰,然后对该类型中成为soap标头的成员使用MessageHeaderAttribute
并对要成为消息的soap正文部分的成员使用MessageBodyMemberAttribute属性休息
如图所示:

虽然中间某些成员为私有的(private),但应为应用了消息属性修饰,所以可以忽略这个访问级别,因为它都会被序列化(数据协定也是如此)
接下来使用代码示例消息协定 先看项目工程结构:

服务端代码 Iservice.cs:
[ServiceContract]
public interface ICalculator
{
[OperationContract]
MyMessage Calculate(MyMessage request); //使用MyMessage这个消息协定 因为返回值和参数都是消息协定 并且只有一个参数 所以这个基于消息的服务协定成立
}
/// <summary>
/// 消息协定 可以用来作为以消息方式传输到客户端的承载类型 并且wcf的基础结构可以去序列化它
/// </summary>
[MessageContract]
public class MyMessage
{
private string operation;
private double n1;
private double n2;
private double result;
//Constructor
public MyMessage() { }
//Constructor
public MyMessage(double n1, double n2, string operation, double result)
{
this.n1 = n1;
this.n2 = n2;
this.operation = operation;
this.result = result;
}
//Constructor
public MyMessage(MyMessage message)
{
this.n1 = message.n1;
this.n2 = message.n2;
this.operation = message.operation;
this.result = message.result;
}
[MessageHeader]
public string Operation //标头
{
get { return operation; }
set { operation = value; }
}
[MessageBodyMember]
public double N1 //正文
{
get { return n1; }
set { n1 = value; }
}
[MessageBodyMember]
public double N2 //正文
{
get { return n2; }
set { n2 = value; }
}
[MessageBodyMember]
public double Result //正文
{
get { return result; }
set { result = value; }
}
[MessageHeader(MustUnderstand = true)]
public string str;
}
service.cs:
public class CalculatorService : ICalculator
{
public MyMessage Calculate(MyMessage request)
{
MyMessage response = new MyMessage(request);
switch (request.Operation)
{
case "+":
response.Result = request.N1 + request.N2;
break;
case "-":
response.Result = request.N1 - request.N2;
break;
case "*":
response.Result = request.N1 * request.N2;
break;
case "/":
response.Result = request.N1 / request.N2;
break;
default:
response.Result = 0.0D;
break;
}
return response;
}
}
app.config:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.web>
<compilation debug="true" />
</system.web>
<!-- 部署服务库项目时,必须将配置文件的内容添加到
主机的 app.config 文件中。System.Configuration 不支持库的配置文件。-->
<system.serviceModel>
<services>
<service name="WcfServiceLibrary1.CalculatorService">
<endpoint address="" binding="wsHttpBinding" contract="WcfServiceLibrary1.ICalculator">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
<host>
<baseAddresses>
<add baseAddress="http://localhost:8732/Design_Time_Addresses/WcfServiceLibrary1/CalculatorService/" />
</baseAddresses>
</host>
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<!-- 为避免泄漏元数据信息,
请在部署前将以下值设置为 false 并删除上面的元数据终结点 -->
<serviceMetadata httpGetEnabled="True"/>
<!-- 要接收故障异常详细信息以进行调试,
请将以下值设置为 true。在部署前设置为 false
以避免泄漏异常信息-->
<serviceDebug includeExceptionDetailInFaults="False" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
</configuration>
客户端代码 Program.cs:
class Program
{
static void Main(string[] args)
{
CalculatorClient client = new CalculatorClient();
MyMessage request = new MyMessage();
request.N1 = 100;
request.N2 = 15.99;
request.Operation = "+";
MyMessage response = ((ICalculator)client).Calculate(request);
Console.WriteLine("Add({0},{1}) = {2}", request.N1, request.N2, response.Result);
request = new MyMessage();
request.N1 = 145D;
request.N2 = 76.54D;
request.Operation = "-";
response = ((ICalculator)client).Calculate(request);
Console.WriteLine("Subtract({0},{1}) = {2}", request.N1, request.N2, response.Result);
request = new MyMessage();
request.N1 = 9D;
request.N2 = 81.25D;
request.Operation = "*";
response = ((ICalculator)client).Calculate(request);
Console.WriteLine("Multiply({0},{1}) = {2}", request.N1, request.N2, response.Result);
request = new MyMessage();
request.N1 = 22D;
request.N2 = 7D;
request.Operation = "/";
response = ((ICalculator)client).Calculate(request);
Console.WriteLine("Divide({0},{1}) = {2}", request.N1, request.N2, response.Result);
client.Close();
Console.WriteLine();
Console.WriteLine("Press <ENTER> to terminate client.");
Console.ReadLine();
}
}
在消息协定内部使用数组
1 使用MessageHeaderAttribute:

2 使用MessageHeaderArrayAttribute:

相信区别已经很明显了
在消息协定内部使用自定义类型:
和上面的代码差不多,就是加一个数据协定,然后在消息协定内创建一个此数据协定类型的成员
数据协定:
[DataContract]
public class ExtType
{
public ExtType() { }
public ExtType(string _name1,string _name2)
{
name1 = _name1;
name2 = _name2;
}
private string name1;
private string name2;
[DataMember]
public string Name1
{
get { return name1; }
set { name1 = value; }
}
[DataMember]
public string Name2
{
get { return name2; }
set { name2 = value; }
}
}
为上面的消息协定增加成员:
[MessageBodyMember]
public ExtType ExtType
{
get { return et; }
set { et = value; }
}
稍微修改下服务的实现:
public class CalculatorService : ICalculator
{
public MyMessage Calculate(MyMessage request)
{
MyMessage response = new MyMessage(request);
switch (request.Operation)
{
case "+":
response.Result = request.N1 + request.N2;
break;
case "-":
response.Result = request.N1 - request.N2;
break;
case "*":
response.Result = request.N1 * request.N2;
break;
case "/":
response.Result = request.N1 / request.N2;
break;
default:
response.Result = 0.0D;
break;
}
// ************
if (!request.ExtType.Equals(null))
response.ExtType = new ExtType(request.ExtType.Name1 + "11", request.ExtType.Name2 + "22");
// ************
return response;
}
}
意思就是 如果消息协定内的这个ExtType成员不为空,就将ExtType成员类的某些字段值追加一些字符串
很简单明了吧 ,若要在消息协定内使用自定义类型,实际上就是追加一个数据协定成员 (数据协定类使用自定义成员也是如此)
对消息部分进行签名和加密
通过在MessageHeaderAttribute和MessageBodyMemberAttribute属性上设置otectionLevel来完成
None(不加密和签名)
Sign(仅数字签名)
EncryptAndSign(加密并数字签名)
默认值为None
note:若要让这些安全功能起作用,必须正确配置绑定和行为。如果在没有正确配置的情况下使用这些安全功能,则会在验证时引发异常
有一点需要注意的是,对于消息头,会分别为每个消息头确定其保护级别,就是每个消息头的保护级别都是单独的、独立的
但正文有点不一样,如果有多个正文,只要有任何一个正文的保护级别比较高,整个正文都保护级别都会被最高的那个给覆盖

recordID标头未受保护,patienName为signed,SSN进行了加密和签名。
即使comments和diagnosis正文指定的保护级别低,因为有一个正文部分medicaHistory应用了EncryptAndSign,所以整个消息正文都会默认应用EncryptAndSign。
安全的部分以后在讨论 这里主要介绍消息协定的部分
控制标头和正文部分的名称与命名空间:
在属性上使用Name和Namespace设置 如图:

对应序列化soap的xml是:

整个的消息协定都会包含在那个<s:Body里面
如果我们不想用默认的这种包装方式,也可以自定义,自定义方式就是在[MessageContract]里,将IsWrapped设置为False,然后设置WrapperName和WrapperNamespace
这只要了解下就好了
性能问题:
每个消息头和正文相互独立的进行序列化。因此可以为每个标头和正文重新声明命名空间。为了提高性能,特别是对于消息在网络中的大小
可以将多个标头和正文合并成一个标头和正文

这个就有点浪费 序列化出来有很多节点

比较好的方式就是将正文部分全部合并成一个 通过放在数据协定里 能提高一些性能
MustUnderstand=true:
在标头设置此属性,就代表客户端也必须有这个成员,并且得对此成员赋值(必须的)
比如服务端某消息协定有[DataHeader(MustUnderstand=true)]public string str;
那么客户端的代理类必须得有这个属性,并且这个属性也得有MustUnderstand=true的设置,并且在使用中必须得对str这个属性赋值

浙公网安备 33010602011771号