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;
但它会引发异常。有解决方案吗?
如果FirstName
和Surname
是简单的字符串属性,则不应抛出异常——也许异常来自其他地方?另外,我建议您使用(FirstName + " " + Surname).Trim()
,如果没有提供两个名称,它会为您提供一个没有空格的全名。你得到的异常到底是什么?【参考方案5】:
在使用类实现合同之前定义服务合同(接口)。
【讨论】:
什么意思? DataMember 位于可通过 DataContract 获得的 DTO 类中。我也有一个 ServiceContract,但这在这里并不真正相关 - 是吗? 这是一个数据合同,不是服务合同以上是关于WCF:公开只读 DataMember 属性而不设置?的主要内容,如果未能解决你的问题,请参考以下文章