WCF:如何在没有set的情况下公开只读的DataMember属性?

63

我有一个服务器端的类,我通过[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]作为字段,就像这里所示的那样吗?我也试过这样做,但似乎不关心字段是否为只读?

编辑: 使只读属性变得可读的唯一方法是像这样对其进行修改吗?(不,我不想这样做...)

[DataMember]
public int Id
{
    get { return _id; }
    private set { /* NOOP */ }
}

2
如果您使用WCF在客户端上,则NOOP setter的想法将导致该属性未能正确传输:在DataContract反序列化中,类在不调用任何构造函数的情况下实例化,并且任何DataMember属性的setter随后传递了由该属性的getter在序列化时返回的内容。因此,您的NOOP setter将丢弃该属性,使其值在反序列化时保持为默认值。相反,请编写一个真正有效的私有setter,或将支持变量标记为DataMember而不是标记属性。 - Theodore Murdock
5个回答

51

"服务器端"的类实际上并不会被 "提供" 给客户端。

发生的情况是:基于数据合同,客户端将从服务的 XML 模式创建一个新的独立类。它 无法直接使用 服务器端类!

它将重新从 XML 模式定义创建一个新类,但该模式不包含任何 .NET 特定的内容,如可见性或访问修饰符 - 它毕竟只是一个 XML 模式。客户端类将以具有相同"占用带宽"的方式创建-例如,它基本上序列化为相同的 XML 格式。

无法通过标准 SOAP 服务来传输 有关类的 .NET 特定知识 - 毕竟,您传递的只是序列化的消息 - 没有类!

请查看 Microsoft 的 Don Box 定义的 "SOA 四个原则":

  1. 边界是明确的
  2. 服务是自治的
  3. 服务共享模式和合同,而不是类
  4. 兼容性基于策略

请参阅第 3 点-服务共享模式和合同,而不是 类 - 您只共享数据合同的接口和 XML 模式-就是这样-没有 .NET 类。


2
讲得非常清楚,谢谢你的澄清! - stiank81
39
这些信息很好,但我认为它没有直接回答问题。 - atoumey
6
这一切都说明了当前的情况是多么荒谬。鉴于服务器端类仅用于生成合同,为什么需要设置器呢?如果不是因为DataContractSerializer很挑剔,服务器上的实例可以完全正常地序列化到客户端而无需设置器。只有反向场景才会引起问题。 - piers7
1
同意@atoumey的观点。Marc,你能详细说明一下可能解决OP问题的方案吗? - julealgon

10

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

请记住,WCF并不知道封装。封装是面向对象编程的术语,而不是面向服务架构的术语。

也就是说,要记住该字段对于使用你的类的人是只读的 - 使用服务的任何人都可以在他们的端访问该字段。


啊,是的 - 就像我注意到的那样,我尝试将该字段设置为DataMember,但在客户端上它并没有被公开为只读。但是在客户端上没有办法使其只读吗? - stiank81
5
READONLY是C#术语,不是SOA。你不能使XML的一部分只读。 - Krzysztof Kozmic
谢谢。这似乎是除了创建一个特定于数据的类之外最好的选择... 对于小型应用程序来说,那只是太多额外的代码了。 - ChrisG

9

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

"3. 服务共享模式和契约,而不是类."

如果您不关心这种违规行为,那么您可以按照以下步骤操作:

  1. Move the service and data contracts into a separate (portable) class library. (Let's call this assembly SomeService.Contracts.) This is how you'd define an immutable [DataContract] class:

    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;
        }
    }
    

    Note that [DataMember] is applied to the backing field, and not to the corresponding read-only property.

  2. Reference the contract assembly from both your service application project (I'll call mine SomeService.Web) and from your client projects (mine is called SomeService.Client). This might result in the following project dependencies inside your solution:

    screenshot highlighting the project dependencies in Solution Explorer

  3. Next, when you add the service reference to your client project, make sure to have the option "reuse types" enabled, and ensure that your contract assembly (SomeService.Contracts) will be included in this:

    screenshot highlighting the relevant service reference setting

看这里!Visual Studio不会从服务的WSDL模式生成新的Foo类型,而是重用来自你的契约程序集的不可变Foo类型。

最后警告一下:你已经偏离了在其他答案中引用的服务原则。但是请尽量不要再偏离。你可能会想要开始向数据契约类添加(业务)逻辑;不要这样做。它们应该尽可能保持作为愚蠢的数据传输对象(DTOs)。


8

我在服务层的一个类中有一些属性,我想将它们传递到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");
        }
    }
}

1
我发现这个解决方案存在的问题是,因为你不能同时使用ISerializable和DataContract,所以DataContract也定义了你的序列化。如果你尝试序列化这个对象,在反序列化过程中会抛出异常。因此,从技术上讲,无操作似乎是唯一的选择,而不是抛出异常。 - Tom Lianza
在这种情况下,将“Total”实现为扩展方法非常有效(我理解您撰写此帖子时可能尚未存在扩展方法)。 - Paul Suart
@PaulSuart 我需要它成为数据绑定的属性,所以它必须是一个属性,而且没有所谓的扩展属性 :-( 但是我确实经常忘记扩展方法,除非是在扩展库。 - Simon_Weaver
@Simon_Weaver 我有一个FullName属性,其get方法如下:[DataMember] public string FullName { get { return FirstName + " " + SurName; } } 但它会抛出异常。有什么解决方案吗? - Ehsan Sajjad
如果FirstNameSurname是简单的字符串属性,这不应该抛出异常——也许异常来自其他地方?此外,我建议使用(FirstName + " " + Surname).Trim(),如果两个名称都没有提供,则可以给您一个没有空格的全名。您得到的异常具体是什么? - Simon_Weaver

-3

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


什么意思?DataMember在DTO类中通过DataContract可用。我也有一个ServiceContract,但这与此并不相关 - 是吗? - stiank81
1
这是一个数据契约,而不是服务契约。 - Krzysztof Kozmic

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接