使用SvcUtil工具可以生成一个cs文件,用以提供访问WCF服务要用到的代理类和接口。
By utilizing SvcUtil tool, one can get the .cs file in which proxy classes/interfaces are presented to consume WCF Services.
如果这一cs文件无须经过人工编辑后续处理,那么客户端将来要用的.config配置也无须特殊处理——否则,情况将不一样。
If no manual modification is required to this .cs file, people need not to pay special attention to the .config at the client side -- but if manual modification is required, things get different.
最近,我被要求更新代理类的.cs文件,以反映出服务端的最新变化。然而,我遇到了问题。
I have a trouble recently when I am asked to update the proxy .cs file to reflect the latest change at the service side.
之前的代理类的.cs文件,既包括了SvcUtil工具的生成代码,也包含了手动编辑部分。比较明显的证据是,同样一个命名空间声明语句,重复了很多次。我肯定之前的编码者是先生成了好几段代码再把它们拼接到了一起。
The original one is the mixture of the result of executing SvcUtil tool and manual modification: the same namespace statement repeats here and there -- I am sure someone generated several files then simply placed the content together into this one.
除了这一“拼接”工作,编码者还进行了其他手动编辑——我非常肯定这一点,等会相关证据会展示给大家——这一手动编辑,正是我后来遇到的问题的根源。
Moreover, that guy did some manual modification -- believe me, I will show you the evidance. And that DOES lead to my problem!
之前的代理类代码文件,数据契约类位于统一的一个命名空间内,服务契约类/接口则没有自己的命名空间,直接位于最上层。
Let‘s go back to the original .cs file, we can see the data contract classes are given an uniform namespace, while the service contract classes/interfaces do not have any namespace but are exposed to the root.
为了最小化工作量,我决定维持这一层次结构安排。为了避免将来维护时不必要的手工劳动,我写了一个批处理,通过一次调用SvcUtil来生成最终的代理文件。
To minimize the possible change, I decided to keep such a structure. And, to avoid unnecessary manual work in the future maintainance, I wrote a .bat file in which a single call to SvcUtil is performed to generate the final proxy .cs file.
幸运的是,服务端发布的数据契约和服务契约,各自位于不同的命名空间下。所以我可以在SvcUtil中利用多个/namespace选项[1]来生成如此层次结构要求的代码:
Fortunately, the data contracts and the service contracts are published with different namespaces, so I can use multiple /namespace options in executing SvcUtil to present exactly the same structure[1]:
SvcUtil ... /namespace:*,DataContractNameSpaceAtClientSide /namespace:ServiceContractNameSpaceAtServiceSide, ...
我执行了此批处理并提交了新的代理文件。
I excuted the .bat file and committed the new proxy file.
似乎一切正常。然而测试人员告诉我,和代理相关的功能彻底不能用了。错误消息说:
Every thing is OK, right? But NOT -- the tester told me that the function involved with the proxy crashed, with error message saying
InvalidOperationException - Could not find default endpoint element that references contract ‘IService‘ in the ServiceModel client configuration section. This might be because no configuration file was found for your application, or because no endpoint element matching this contract could be found in the client element. ...
我的变更有什么问题?我记得完全没有改过config文件啊。
What‘s wrong with my change? I did not do any modification towards the .config file.
我不得不回头仔细比较之前的代码和我生成的新代理文件,最后发现主要的不同点位于服务契约接口上的System.ServiceModel.ServiceContract属性的ConfigurationName值。
I have to turn back to compare the original proxy file and the new one, at last the main difference is addressed at the value of System.ServiceModel.ServiceContractAttribute.ConfigurationName declared for the service contract interface.
原始文件给的是
The original one is like
[System.ServiceModel.ServiceContractAttribute(ConfigurationName = "ClientProxies.IService")] public interface IService { // Something declared here. }
而我的是
while mine is like
[System.ServiceModel.ServiceContractAttribute(ConfigurationName = "IService")] public interface IService { // Something declared here. }
根据[2]的讨论,SvcUtil工具在生成代码时,System.ServiceModel.ServiceContract属性的ConfigurationName值永远等于其所装饰的类/接口的完整类名。在服务契约接口没有上层命名空间的情况下,我的代码是SvcUtil工具的原生结果,相反地,原始代码的方式明显是人工干预的结果。
According to the discussion in [2], when utilizing SvcUtil tool, the service contracts in the proxy file, will always take the classes/interfaces full type name as the value of System.ServiceModel.ServiceContractAttribute.ConfigurationName. As the final proxy interface does not have any super namespace, mine code is the nature result of utlizing SvcUtil tool -- and the original one must have been experienced certain manual modification else its ConfigurationName cannot look like that.
这一差异导致了相关功能崩溃的问题吗?
But may such a difference lead to my trouble?
答案是"是"。
The answer is YES.
在config文件中有如下声明:
Let‘s see what is configured in the .config file:
<system.serviceModel> <client> <endpoint contract="ClientProxies.IService" name="IService" /> </client> </system.serviceModel>
根据[3]
According to [3]
…这个endpoint的contract属性声明了它所服务的契约对象。该值与契约对象的ServiceContract属性的ConfigurationName值相对应——若此ConfigurationName
未提供,则默认取契约对象的完整类名。…
... The contract attribute specifies which contract the endpoint is exposing. This value maps to the ConfigurationName of the ServiceContractAttribute.
The default value is the full type name of the class that implements the service. ...
所以即使没有所谓的ClientProxies.IService存在,系统仍然能够通过将契约对象的ServiceContract属性的ConfigurationName设置成该值来实现定位。这就是为什么原来的代理能工作而我的不能的原因。
although there is no such a ClientProxies.IService type but only IService declared, the application will also be able to make use of it as long as it is decorated with the System.ServiceModel.ServiceContractAttribute of ConfigurationName = "ClientProxies.IService". This is why the original proxy worked.
真惭愧,和WCF打交道差不多十年了,却到今天才了解这点。
Shame me, after nearly ten years working with WCF, only till today I know the connection between the system.serviceModel\client\[email protected] and System.ServiceModel.ServiceContractAttribute.ConfigurationName!
[1] http://stackoverflow.com/questions/1103686/use-svcutil-to-map-multiple-namespaces-for-generating-wcf-service-proxies
[2] https://social.msdn.microsoft.com/Forums/vstudio/en-US/14ff3b6e-22a9-4a4a-b12f-1cffc8ddf6da/svcutilexe-issue-with-configuration-name?forum=wcf
[3] https://msdn.microsoft.com/en-us/library/ms731745(v=vs.110).aspx