从基于.NET Framework迁移到Mono平台,除了经常会遇到大小写敏感的问题之外,还会经常遇到一些平台不兼容性的问题,这也是我的经历当中,遇到的另一个比较麻烦的问题。其实关于Mono平台的兼容性,在官方的文档中已经释放了比较明确的信息,他们就明确的一句话来描述Mono的兼容性:“Everything in .NET 4.0 except WPF, EntityFramework and WF, limited WCF.”。但是实际上并不是这么简单的!在Kooboo CMS中,目前没有用过WPF、EF、WF、WCF,但是仍然遇到了几个由于平台的兼容性而导致的问题。这些不兼容,本身就是Mono设计的意图,出于操作系统平台的差异性,Mono可能会选择对部分功能的不兼容;我还遇到了一些在.NET Framework中编译正常的代码,但是在Mono中无法编译通过,这种可能是对编译器严谨性设计上的不同;同时,对于已支持的API,虽然功能确实是支持,但是我们还是会得到一些与.NET Framework不大一样的结果而造成不兼容的情况出现。
对于程序集出现不兼容的情况时,不像.NET Framework在启动时经常出现的各种程序集无法载入而让整个站点无法启动,MONO的策略是比较好的。我们完全可以把在.NET中编译好的Web站点,直接放到Mono运行时去执行。这时候,假定你程序中使用了MONO不支持的某些程序集或API的时候,它并不会马上就告诉你程序无法启动,而是只要你还没有用到这些功能,程序就还可以正常运行。所以我们会经常发现,我们发布好的站点,可能可以直接在MONO中跑起来,但是当我们把源码用MonoDevelop打开时,却无法编译通过,出现不少的编译错误。我很喜欢这点设计。
尽管如此,要让ASP.NET站点可以完全兼容MONO,调试是必不可少的。所以我们必须保证我们的源码可以在MonoDevelop中正常打开,并且完全编译通过。下面的几点不兼容或是在编译时就发现,或是程序出错了错误后,通过MonoDevelop的调试发现并且解决。让我们一起来看一下吧:
- ASP.NET HealthMonitoring的不兼容。在源码用MonoDevelop打开之后,一编译就发现好多好多的错误,其中大部分的错误是出现在所重写的几个HealthMonitoring的类上面。经常一段时间的研究发现,Mono在System.Web.Management的空间下,只有少数的几个类,而且这些类的大部分都没有具体的实现。对于这点不兼容,自然没什么好说的,也没有好的解决办法,再找一个好的替代方案。不过,我相信这个功能应该是可以实现的,除了Eventlog有系统平台特性之外,应该没有更多的平台依赖。
- 在编译错误中,有一行代码,在.NET中运行正常,在MONO中却出错,提示我们需要做一下类型转换。为了更好的说明这个编译错误,我把代码原型提炼出来:
public interface IReadonly { string Name{get;} } public class BaseClass { public string Name { get; set; } } public class Class1:BaseClass,IReadonly { } class MainClass { public static void SetName<T>(T o) where T:BaseClass,IReadonly { o.Name= ""; } }
这段代码中,o.Name=“”这行在Mono中会提示我们“Ambiguity between BaseClass.Name and IReadonly.Name”。对于这个问题,解决方案很简单,只需要做对o作一下类型转换,转成BaseClass的类型再赋值就可以通过了。我本身对编译器的行为了解不是特别多,所以讲不出个所以然出来。只能说,这是两种编译器在类型处理严谨性上面的不同吧。
- 对于Mono支持的API,我们仍然也需要经过运行测试之后才能保证它是否工作正常。相同的一个API,不同的平台,产生不同的结果,这个是很容易理解的。我这边就遇到两个这样的问题。
- 在使用KeyedHashAlgorithm对字符串进去HASH的结果不同。因为我们使用了KeyedHashAlgorithm对用户密码进行一次加密,但是相同的用户数据和输入密码在MONO运行时,总是提示密码不正确。经过调试发现,是由于.NET和MONO所使用的密钥长度不同,还好只是使用了密钥长度。我们只需要让它们使用一样的长度就可以了,加上这行代码:algorithm.Key = new byte[64];
- 我使用了DataContractSerializer来序列化对象。在序列化普通对象的时候,它们的格式都是一致的。但是在序列化Dictionary对象的时候,.NET和Mono就会有一些差异,这些差异主要集中在XML名称空间上。在.NET中,序列化的根结点的名称空间是http://schemas.microsoft.com/2003/10/Serialization/Arrays,而MONO序列化后的根结点名称空间为:http://schemas.datacontract.org/2004/07/System.Collections.Generic。这就造成了平台之间的数据切换的不兼容。不过,经过一段时间的研究还是找到的解决办法,只需要在实例化DataContractSerializer的时候指定使用.NET的根结点名称空间就可以解决问题:
DataContractSerializer ser = new DataContractSerializer(typeof(List<Dictionary<string,object>>),"ArrayOfArrayOfKeyValueOfstringanyType","http://schemas.microsoft.com/2003/10/Serialization/Arrays",new Type[]{typeof(Dictionary<string,object>)})
对于序列化的不兼容,这边还有另一个问题,就是对IDictionary的处理手法上Mono和.NET还是有一些不同的。在.NET中,对象的属性中包含IDictionary对象是可以正常序列化和反序列化,但是在MONO运行时就会提示“'this' type cannot be an interface itself”。解决这个错误的办法就是把IDictionary改成为Dictionary,用具体的类型,而不是用接口。
以上的这些问题都是我目前能记起来的所遇到的平台的不兼容问题。大家可以看到,除了MONO本身不支持的功能和API之外,其它的兼容性问题经过一段时间的调试和研究之后都还是可以顺利解决的。由此我相信,要让一个普通的ASP.NET站点兼容MONO,应该不仅仅只存在于理论上的可能了。
本来计划,本篇是作为一个结束篇来写的。但是在写的过程中发现,还有一些其它方面的心得还漏掉很多,比如使用MonoDevelop的经验,调试ASP.NET MVC程序的经验等等。这些都与Windows/.NET平台还是有一些区别的。所以下篇准备分享和记录一下这方面的经验。