WCF中因序列化问题引起的异常和错误。
这段时间一直在忙着赶项目,由DAL+WCF+WinForm几层组成。其中数据库使用的MySQL+MySQL.Data.dll Driver。不过在使用中,经常碰到如下错误:
接收对 http://localhost/***.svc 的 HTTP 响应时发生错误。这可能是由于服务终结点绑定未使用 HTTP 协议造成的。这还可能是由于服务器中止了 HTTP 请求上下文(可能由于服务关闭)所致。有关详细信息,请参见服务器日志。
有时也会出现如下错误
基础连接已经关闭: 连接被意外关闭。
经过仔细核对发现,每当发生MySql.Data.MySqlClient.MySqlException异常时,就会报这个错误。网上找了半天也没有找到解决方案,后来使用Trace Viewer跟踪才发现是MySqlException序列化时出现了问题:
System.ServiceModel.CommunicationException: 尝试对参数 http://tempuri.org/ 进行序列化时出错: result。InnerException 消息是“不应为数据协定名称为“MySqlException:http://schemas.datacontract.org/2004/07/MySql.Data.MySqlClient”的类型“MySql.Data.MySqlClient.MySqlException”。请考虑使用 DataContractResolver,或将任何未知类型以静态方式添加到已知类型的列表。例如,可以使用 KnownTypeAttribute 特性,或者将未知类型添加到传递给 DataContractSerializer 的已知类型列表。”。有关详细信息,请参见 InnerException。 System.Runtime.Serialization.SerializationException:.......
其中result是一个存储操作结果的类实例,它横跨DAL、WCF、WinForm三层,用以存储一个完整操作中发现的异常及信息。里面对可能发生的异常做了包装,它的结构如下(其它属性及构造函数等没有列出):
[DataContract]
public class Result
{
private bool success;
[DataMember]
public boll Success
{
get {return this.success;}
set {this.success = value;}
}
private Exception exception;
[DataMember]
publice Exception Exception
{
get {return this.exception;}
set {this.exception = value;}
}
}
当DAL层中涉及SQL语句的执行发生错误时,result.Exception类型实际为MySqlException。虽然在Result类定义时已经声明[DataContract],不过序列化时仍然出错,头疼!查看MySqlException的定义如下:
[Serializable]
public sealed class MySqlException : DbException
{
public int Number { get; }
}
明明是已经声明了[Serializable]特性,而且查看MySQL.Data.dll 文档,发现其在0.7版本的时候,就已经支持序列化了,却仍然不可在WCF中序列化,只能再找原因。
根据错误的提示,在IService的契约声明接口中,加上了[ServiceKnownType(typeof(MySql.Data.MySqlClient.MySqlException))]声明,可到头来结果还是一样不起作用。但是如果发生的异常不是MySqlException类型时,就不会报序列化错误。
对比Exception和MySqlException的实现,发现虽然都声明了[Serializable]特性,但MySqlException却没有实现序列化和逆序列化的函数。问题很可能出现在这个地方。
不过由于没有MySQL.Data.dll 的源代码,无法让添加这两个函数,只得新建一个异常类CustomException存放在Result。其代码如下:
[Serializable]
public class CustomException : ISerializable,Exception
{
private string message;
public override string Message
{
get
{
return this.message;
}
}
private string stackTrace;
public override string StackTrace
{
get
{
return this.stackTrace;
}
}
public CustomException() { }
public CustomException(string exceptionMessage, string exceptionStackTrace)
{
this.message = exceptionMessage;
this.stackTrace = exceptionStackTrace;
}
/*
以下序列化和逆序列化中,只保存了Message和StackTrace
*/
protected CustomException(SerializationInfo info, StreamingContext context)
{
this.message = info.GetString("Message");
this.stackTrace = info.GetString("StackTraceString");
}
public override void GetObjectData(SerializationInfo info, StreamingContext context)
{
info.AddValue("Message", this.message);
info.AddValue("StackTraceString", this.stackTrace);
}
public override string ToString()
{
if (string.IsNullOrEmpty(this.message))
return string.Empty;
return this.message.ToString();
}
}
之所以override定义Message和StackTrace,是为能对它们俩重写,因为Exception类中,定义的StackTrace是只读的,Message也只能在构造函数中赋值。此外,此处只定义了这两个属性,是因为项目中只用到了Exception中的这两个属性,一般情况下,这两个属性也已经足够查找异常信息了。
当然最主要的地方,还是要实现一个构造函数,用来序列化,以及GetObjectData 方法设置序列化对象的替代值。
再次跟踪执行,当产生MySqlException时,Result对象可以正常从DAL->WCF->WinForm。OK啦!
总结:这个问题,的确是折磨了我好几天,由于是对WCF头一次在项目中使用,对它的了解也不多。如果不是看到使用Trace Viewer,还不会找到问题的本质所在。看来.NET下的异常提示,有时候也不一定就是问题的产生点,就像编译代码一样,少个逗号,提示的信息却是五花八门。
---------------------------------------------