WCF:公开只读 DataMember 属性而不设置?

Posted

技术标签:

【中文标题】WCF:公开只读 DataMember 属性而不设置?【英文标题】:WCF: Exposing readonly DataMember properties without set? 【发布时间】:2010-12-24 19:54:47 【问题描述】:

我有一个服务器端类,我通过 [DataContract] 在客户端提供它。此类有一个只读字段,我想通过属性使其可用。但是,我无法这样做,因为似乎不允许我在没有 get 和 set 的情况下添加 [DataMember] 属性。

那么 - 有没有办法在没有 setter 的情况下拥有 [DataMember] 属性?

[DataContract]
class SomeClass

    private readonly int _id; 

    public SomeClass()  .. 

    [DataMember]
    public int Id  get  return _id;          

    [DataMember]
    public string SomeString  get; set; 

或者解决方案将使用 [DataMember] 作为字段 - (例如显示 here)?也尝试过这样做,但它似乎并不关心该字段是只读的..?

编辑:像这样修改只读属性是唯一的方法吗? (不 - 我不想这样做......)

[DataMember]
public int Id

    get  return _id; 
    private set  /* NOOP */ 

【问题讨论】:

如果您在客户端使用 WCF,您对 NOOP 设置器的想法将导致该属性无法正确传输:在DataContract 反序列化中,类在不调用任何构造函数的情况下被实例化,并且然后将任何DataMember 属性的设置器传递给该属性的获取器在序列化时返回的任何内容。因此,您的 NOOP 设置器将丢弃该属性,将其值保留为反序列化时的默认值。相反,编写一个真正的、有效的私有设置器,或者将支持变量标记为DataMember,而不是标记属性。 【参考方案1】:

您的“服务器端”类不会对客户端“可用”,真的。

发生的情况是这样的:基于数据契约,客户端将从服务的 XML 模式创建一个新的单独类。它不能使用服务器端类本身!

它将根据 XML 模式定义重新创建一个新类,但该模式不包含任何 .NET 特定的东西,如可见性或访问修饰符 - 毕竟它只是一个 XML 模式。客户端类将以这样的方式创建,即它在线路上具有相同的“足迹” - 例如它基本上序列化为相同的 XML 格式。

不能通过标准的基于 SOAP 的服务“传输”有关该类的 .NET 特定知识 - 毕竟,您传递的只是序列化消息 - 没有课!

检查“SOA 的四项原则”(由 Microsoft 的 Don Box 定义):

    界限是明确的 服务是自治的 服务共享架构和契约,而不是类 兼容性基于策略

参见第 3 点 - 服务共享架构和契约,不是类 - 您只共享数据契约的接口和 XML 架构 - 仅此而已 - 没有 .NET 类。

【讨论】:

这很好地解释了它。感谢您的澄清! 这个信息很好,但我认为它不能直接回答问题。 这一切都说明了当前的情况是多么的荒谬。鉴于服务器端类仅用于生成合约,为什么需要设置器?如果不是因为 DataContractSerializer 不严谨,服务器上的实例可以在没有它们的情况下将 很好 序列化到客户端。只有相反的情况才会导致问题。 同意@atoumey。 Marc,您能否详细说明一下 OP 问题的可能解决方案?【参考方案2】:

将 DataMember 属性放在字段而不是属性上。

记住,WCF 不知道封装。封装是 OOP 术语,而不是 SOA 术语。

也就是说,请记住,该字段对于使用您的课程的人来说是只读的 - 任何使用该服务的人都将拥有对该字段的完全访问权限。

【讨论】:

啊,是的 - 就像注意到的那样,我尝试将该字段设置为 DataMember,但它没有在客户端公开为只读。但是没有办法让它在客户端成为只读的呢? 没有。 READONLY 是 C# 术语,而不是 SOA。您不能将 XML 的一部分设为只读 谢谢。除了创建特定于数据的类之外,这似乎是最好的选择……而对于小型应用程序,这只是太多的额外代码。【参考方案3】:

有一种方法可以实现这一点。但请注意,它直接违反了this answer中引用的以下原则:

“3. 服务共享架构和契约,而不是类。”

如果此违规行为与您无关,您可以这样做:

    将服务和数据契约移动到单独的(可移植的)类库中。 (让我们将此程序集称为SomeService.Contracts。)这就是您定义不可变[DataContract] 类的方式:

    namespace SomeService.Contracts
    
        [DataContract]
        public sealed class Foo
        
            public Foo(int x)
            
                this.x = x;
            
    
            public int X
            
                get
                
                    return x;
                
            
    
            [DataMember]  // NB: applied to the backing field, not to the property!
            private readonly int x;
        
    
    

    请注意,[DataMember] 应用于支持字段,应用于相应的只读属性。

    从您的服务应用程序项目(我的称为SomeService.Web)和您的客户端项目(我的称为SomeService.Client)中引用合同程序集。这可能会导致您的解决方案中出现以下项目依赖项:

    接下来,当您将服务引用添加到您的客户端项目时,请确保启用“重用类型”选项,并确保您的合同程序集 (SomeService.Contracts) 将包含在此:

瞧! Visual Studio 不会从服务的 WSDL 架构中生成新的 Foo 类型,而是会重用合同程序集中的不可变 Foo 类型。

最后一个警告:您已经偏离了that other answer 中引用的服务原则。但尽量不要再偏离。您可能很想开始向数据合约类添加(业务)逻辑;别。它们应该尽可能靠近哑数据传输对象 (DTO)。

【讨论】:

【参考方案4】:

我想传递给 Silverlight 的服务层的类中有一些属性。我不想创建一个全新的类。

并不是真正的“推荐”,但将Total 属性传递给silverlight(仅用于可视数据绑定)似乎是两个弊端中较小的一个。

public class PricingSummary

    public int TotalItemCount  get; set;  // doesnt ideally belong here but used by top bar when out of store area

    public decimal SubTotal  get; set; 
    public decimal? Taxes  get; set; 
    public decimal Discount  get; set; 
    public decimal? ShippingTotal  get; set; 
    public decimal Total
    
        get
        
            return + SubTotal
                   + (ShippingTotal ?? 0)
                   + (Taxes ?? 0)
                   - Discount;
        
        set
        
            throw new ApplicationException("Cannot be set");
        
    

【讨论】:

我看到的这个解决方案的问题是,因为你不能同时使用 ISerializable 和 DataContract,DataContract 也定义了你的序列化。如果你试图序列化这个对象,它会在反序列化过程中抛出异常。因此,与抛出异常相比,无操作似乎是唯一的选择。 在这种情况下,将“Total”实现为扩展方法效果很好(我知道扩展方法可能在你写这篇文章时还没有出现)。 @PaulSuart 我需要它作为数据绑定的属性,所以它需要是一个属性,并且没有扩展属性之类的东西 :-( 但是是的,除了扩展时,我确实倾向于忘记扩展方法图书馆 @Simon_Weaver 我的 FullName 属性类似于:[DataMember] public string FullName get return FirstName + " " + SurName; 但它会引发异常。有解决方案吗? 如果FirstNameSurname 是简单的字符串属性,则不应抛出异常——也许异常来自其他地方?另外,我建议您使用(FirstName + " " + Surname).Trim(),如果没有提供两个名称,它会为您提供一个没有空格的全名。你得到的异常到底是什么?【参考方案5】:

在使用类实现合同之前定义服务合同(接口)。

【讨论】:

什么意思? DataMember 位于可通过 DataContract 获得的 DTO 类中。我也有一个 ServiceContract,但这在这里并不真正相关 - 是吗? 这是一个数据合同,不是服务合同

以上是关于WCF:公开只读 DataMember 属性而不设置?的主要内容,如果未能解决你的问题,请参考以下文章

在 Web 服务中忽略 DataMember 名称属性

WCF 数据合同

WCF:属性与成员的 DataMember 属性

有没有办法在 WCF 的数据合同中使用 DataMember 装饰多个属性?

不需要 WCF DataMember 时

将 DataMember 添加到 WCF 中的现有 DataContract