WCF 设置/获取未执行

我对WCF有点新意,我认为我完全不了解DataContracts的交易。 我有这个’RequestArray’类:

[DataContract] public class RequestArray { private int m_TotalRecords; private RequestRecord[] m_Record; [System.Xml.Serialization.XmlElement] [DataMember] public RequestRecord[] Record { get { return m_Record; } } [DataMember] public int TotalRecords { get { return m_TotalRecords; } set { if (value > 0 && value <= 100) { m_TotalRecords = value; m_Record = new RequestRecord[value]; for (int i = 0; i < m_TotalRecords; i++) m_Record[i] = new RequestRecord(); } } } } 

这个想法是客户说requestArray.TotalRecords=6; Record数组将被分配和初始化(我意识到我隐藏了一个分配后面的实现,这是我无法控制的)。

问题是当客户端执行此操作时,不会调用TotalRecord的设置代码,服务中的断点也会确认。 相反,已经生成了某种类型的通用setter而是被调用。 如何让客户端使用我的setter?

编辑:看起来我并没有完全了解[DataContract]的工作原理,但是客户端不会执行此代码是有道理的。 就像我在评论中提到的那样,如果我’手动’执行setter的工作,我会看到设置代码在我调用服务函数时正确执行。

序列化我仍然不确定。 RequestRecord []数组的内容不会被转移。 Record类有setter / getters,我觉得我需要一个辅助函数来帮助它序列化整个类。

感谢你的帮助!

客户端代理将在客户端和服务器上执行相同代码的前提是有缺陷的,因为WCF基于接口。 我在下面的子弹#2中解释了这一点。

共享WCF接口和实现的规则

如果要共享数据协定的实现,则需要将RequestArray类分解为一个类库,该类包含NOTHING但数据协定类,包括可能还有RequestRecord类。

我遵守的规则:

  1. 将所有数据合同(100%)分组到一个或多个程序集中,没有例外。

  2. 将所有服务合同分组到一个或多个程序集中。

  3. 将所有服务类型(即实现服务合同的类)分组到一个或多个程序集中。

  4. 将所有客户机通道代理(即,调用服务协定接口上定义的方法的类)分组到一个或多个程序集中。

  5. 在通用框架中,所有客户端软件都作为WCF服务运行(我避免双工连接),合并规则2,3和4是安全的,这样服务合同,服务类型和通道代理就可以组合成一个程序集。

将接口分离为更灵活的依赖关系链的主要原因是可以将有限的一组程序集部署到客户端,而不会暴露不必要的和潜在的专有实现细节。 另一个原因是它使重构变得如此简单,特别是在您希望通过inheritance或委派实现或扩展通用框架的情况下。

检查代码

RequestArray的代码有一些大的问题……

  1. 当反序列化DataContract实例时,setter逻辑将覆盖m_Record数组变量的任何修改元素。 这违反了反序列化原则。

  2. Record属性将无法反序列化,因为RequestArray类上的Record属性是只读的(因为它没有setter)。 通常,我发现对于DataContract类,只读属性的最佳方法只是一种方法。 养成处理数据合同的习惯是一个坏主意,就像它们不仅仅是比特桶一样。 属性基本上是动态创建专门用于数据序列化和反序列化的接口定义。 我认为将线上的数据视为对象是错误的。 相反,它是一种选择性的方式来指定需要通过线路持久存在的对象的相关数据部分。

  3. 如果TotalRecords属性结束(正确)只允许设置m_TotalRecords变量,则TotalRecords属性变得危险,因为它将完全独立于内部数组。 为了让我在我的示例代码(下面)中可以接受这个工作,我必须使用if (m_TotalRecords == 0)屏蔽该集合。 在我保存以供将来使用的示例代码中,我完全注释掉TotalRecords属性,但是我留下m_TotalRecords只是为了说明私有对象实际上是通过线路保留的。

固定代码

我改编了bendewey的示例代码(谢谢!)并提出了这个完整的测试。 注意:我必须定义RequestRecord。 另外,请参阅代码注释。 如果有任何错误或任何不清楚的地方,请告诉我。

 #region WCFDataContractTest [DataContract] // The enclosed type needs to also be attributed for WCF public class RequestRecord { public RequestRecord() { } [DataMember] // This is CRUCIAL, otherwise the Name property will not be preserved. public string Name { get; set; } } [DataContract] // Encloses the RequestRecord type public class RequestArray { private int m_TotalRecords; // should be for internal bookkeeping only private RequestRecord[] m_Record; [System.Xml.Serialization.XmlElement] [DataMember] public RequestRecord[] Record { get { return m_Record; } // deserialization will not work without the set set { m_Record = value; } } [DataMember] // is not really needed public int TotalRecords { get { return m_TotalRecords; } set { if (m_TotalRecords == 0) m_TotalRecords = value; } } // The constructor is not called by the deserialization mechanism, // therefore this is the right place to specify the array size and to // perform the array initialization. public RequestArray(int totalRecords) { if (totalRecords > 0 && totalRecords <= 100) { m_TotalRecords = totalRecords; m_Record = new RequestRecord[totalRecords]; for (int i = 0; i < m_TotalRecords; i++) m_Record[i] = new RequestRecord() { Name = "Record #" + i.ToString() }; m_TotalRecords = totalRecords; } else m_TotalRecords = 0; } } public static void TestWCFDataContract() { var serializer = new DataContractSerializer(typeof(RequestArray)); var test = new RequestArray(6); Trace.WriteLine("Array contents after 'new':"); for (int i = 0; i < test.Record.Length; i++) Trace.WriteLine("\tRecord #" + i.ToString() + " .Name = " + test.Record[i].Name); //Modify the record values... for (int i = 0; i < test.Record.Length; i++) test.Record[i].Name = "Record (Altered) #" + i.ToString(); Trace.WriteLine("Array contents after modification:"); for (int i = 0; i < test.Record.Length; i++) Trace.WriteLine("\tRecord #" + i.ToString() + " .Name = " + test.Record[i].Name); using (var ms = new MemoryStream()) { serializer.WriteObject(ms, test); ms.Flush(); ms.Position = 0; var newE = serializer.ReadObject(ms) as RequestArray; Trace.WriteLine("Array contents upon deserialization:"); for (int i = 0; i < newE.Record.Length; i++) Trace.WriteLine("\tRecord #" + i.ToString() + " .Name = " + newE.Record[i].Name); } } #endregion 

运行TestWCFDataContract后,此示例程序的列表是:

'new'后的数组内容:

  Record #0 .Name = Record #0 Record #1 .Name = Record #1 Record #2 .Name = Record #2 Record #3 .Name = Record #3 Record #4 .Name = Record #4 Record #5 .Name = Record #5 

修改后的数组内容:

  Record #0 .Name = Record (Altered) #0 Record #1 .Name = Record (Altered) #1 Record #2 .Name = Record (Altered) #2 Record #3 .Name = Record (Altered) #3 Record #4 .Name = Record (Altered) #4 Record #5 .Name = Record (Altered) #5 

反序列化时的数组内容:

  Record #0 .Name = Record (Altered) #0 Record #1 .Name = Record (Altered) #1 Record #2 .Name = Record (Altered) #2 Record #3 .Name = Record (Altered) #3 Record #4 .Name = Record (Altered) #4 Record #5 .Name = Record (Altered) #5 

我只是进行了一点测试,然后调用了setter。 你如何反序列化对象?

 class Program { static void Main(string[] args) { var serializer = new DataContractSerializer(typeof(Employee)); var employee = new Employee() { Name="Joe" }; using (var ms = new MemoryStream()) { serializer.WriteObject(ms, employee); ms.Flush(); ms.Position = 0; var newE = serializer.ReadObject(ms) as Employee; } Console.ReadKey(); } } [DataContract] public class Employee { private string _name; [DataMember] public string Name { get { return _name; } set { _name = value; } } } 

假设您正在创建对WCF服务的服务引用,我认为另一个注释是正确的,因为在客户端上您实际上正在访问作为代理类生成的一部分创建的生成类。

有一种方法可以实现您想要实现的目标。 我会从架构的角度来质疑它,它只适用于.NET客户端。

  • 将服务接口,参数类型和返回类型(即DataContracts)隔离到程序集中。 我称之为合同大会

  • 将您的服务实现移动到一个单独的程序集中,我称之为实现程序集 ,并引用合同程序集

  • 在客户端中创建对Contract Assembly的引用

  • 在客户端手动编写通道的实例化…这样的事情(你必须查找确切的语法):IMyService myService = new ChannelFactory ()。CreateChannel();

post条件应该是在客户端上成功调用DataContract类中编写的行为,因为使用了本地类型引用。

快乐的编码!

听起来你可能会混淆服务器端和客户端function。 如果您通过Visual Studio生成了客户端类,那么您的Set逻辑将不会被转移。 您可以通过打开ProjectFolder \ Service References \ SomeServiceName \ References.cs文件并查看为您生成的对象VS的defenition来检查这一点。

如果您的客户端和服务器与您的Set逻辑共享一个公共dll,那那将是非常奇怪的。 我需要看到更多代码调用它。

编辑:作为附录,如果您希望您的服务器和客户端在其签约对象上共享相同的逻辑,那么最好的做法是为您的所有datacontracts和servicecontracts留出一个单独的dll,然后在这两个项目中包含此dll。 我还发现让VS为你生成代理类是好的和快速的,但从长远来看会引起严重的问题。

客户端必须使用相同的类实现。 当您向特定应用程序添加服务引用时,当VS生成客户端类时,数据协定的属性背后的实现细节不会添加到类中。 将公开的类编译到单独的类库中,在客户端项目中引用该类库,以及在向项目添加服务引用时,请确保在“高级…”选项卡下“重用引用的程序集中的类型”被检查。