【问题标题】:WCF [DataContract] set/get not getting executedWCF [DataContract] 设置/获取未执行
【发布时间】:2009-02-03 19:55:20
【问题描述】:

我对 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] 的工作原理,但客户端不会执行此代码是有道理的。就像我在评论中提到的那样,如果我“手动”完成设置器的工作,我会看到设置代码在我调用服务函数时会正确执行。

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

感谢大家的帮助!

【问题讨论】:

  • 我注意到代码的序列化问题,我已经发布了一个完整的答案以及可以正确序列化的固定示例代码。

标签: c# wcf


【解决方案1】:

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

共享 WCF 接口和实现的规则

如果您想共享数据协定的实现,您需要将 RequestArray 类分解到一个类库中,该类库只包含数据协定类,可能还包括 RequestRecord 类。

我遵守的规则:

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

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

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

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

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

将接口分离成更灵活的依赖链的主要原因是可以将一组有限的程序集部署到客户端,而不会暴露不必要的和潜在的专有实现细节。另一个原因是它使重构变得非常容易,尤其是在您想通过继承或委托来实现或扩展通用框架的情况下。

检查代码

RequestArray 的代码存在一些大问题...

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

  2. Record 属性将无法反序列化,因为 RequestArray 类的 Record 属性是只读的(因为它缺少 setter)。 一般来说,我发现对于 DataContract 类,只读属性的最佳方法只是一个方法。 养成将数据合同视为比比特桶更重要的东西的习惯是一个坏主意.属性基本上做的是动态创建专门用于数据序列化和反序列化的接口定义。 我认为将线路上的数据视为对象是错误的。相反,它是一种选择加入的方式,用于指定需要通过网络持久保存的对象的相关数据部分。

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

固定代码

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

#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

【讨论】:

  • 其实所有的答案都非常有帮助,但这是最完整的。谢谢大家,它现在就像一个魅力!
【解决方案2】:

我刚刚运行了一个小测试,它调用了 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; }
    }
}

【讨论】:

  • +1 因为您的代码启发了我编写一些测试代码并发布我即将发布的答案...
  • 我以前从未像这样费心测试我的对象的序列化,因为通常我将我的 DataContract 类视为只包含位的属性包,我相信这主要是意图。但是,现在我正在将此测试合并到我的对象验证代码中。
【解决方案3】:

假设您正在创建对 WCF 服务的服务引用,我相信另一条评论是正确的,因为在客户端上您实际上正在访问已作为代理类生成的一部分创建的生成类。

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

  • 将您的服务接口、参数类型和返回类型(即您的 DataContracts)隔离到一个程序集中。我称之为合同程序集

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

  • 在客户端创建对合约组件的引用

  • 在客户端手动编码通道的实例化...类似这样(您必须查找确切的语法): IMyService myService = new ChannelFactory().CreateChannel();

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

编码愉快!

【讨论】:

    【解决方案4】:

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

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

    编辑: 作为附录,如果您希望您的服务器和客户端在他们的合同对象上共享相同的逻辑,最好的办法是为您的所有数据合同和服务合同预留一个单独的 dll,然后在两个项目中包含这个 dll。我还发现让 VS 为您生成代理类既好又快,但从长远来看,会让人头疼。

    【讨论】:

    • 你是对的。我在客户端手动执行了这些操作,并注意到 setter 实际上被调用了——一旦对象被传递到服务器端。我不确定 DLL 解决方案是否实用。我认为我对 DataContracts 做出了错误的假设。
    • 起初我认为自动生成的类会很好,但发现 VS 引入了太多细微差别。使用单独的 dll,您可以确切地知道序列化在双方的外观。它还减少了双方所需的维护量。
    • 我将在此处添加此内容,因为我无法将其添加到 jonathan.fenwick 的帖子中:我编写了这个通用 WCF 客户端以希望简化关闭通道:aintisaword.wordpress.com/2008/11/04/generic-client-for-wcf
    【解决方案5】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-11
      • 1970-01-01
      • 2011-02-04
      • 2013-08-31
      • 2013-10-07
      • 1970-01-01
      • 2016-02-18
      相关资源
      最近更新 更多