【问题标题】:WCF service proxy not setting "FieldSpecified" propertyWCF 服务代理未设置“FieldSpecified”属性
【发布时间】:2009-11-05 12:47:57
【问题描述】:

我有一个 WCF DataContract,如下所示:

namespace MyCompanyName.Services.Wcf
{
  [DataContract(Namespace = "http://mycompanyname/services/wcf")]
  [Serializable]
  public class DataContractBase
  {
    [DataMember]
    public DateTime EditDate { get; set; }

    // code omitted for brevity...
  }
}

当我在 Visual Studio 中添加对该服务的引用时,会生成此代理代码:

/// <remarks/>
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "2.0.50727.3082")]
[System.SerializableAttribute()]
[System.Diagnostics.DebuggerStepThroughAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(Namespace="http://mycompanyname/services/wcf")]
public partial class DataContractBase : object, System.ComponentModel.INotifyPropertyChanged {

    private System.DateTime editDateField;

    private bool editDateFieldSpecified;

    /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Order=0)]
    public System.DateTime EditDate {
        get {
            return this.editDateField;
        }
        set {
            this.editDateField = value;
            this.RaisePropertyChanged("EditDate");
        }
    }

    /// <remarks/>
    [System.Xml.Serialization.XmlIgnoreAttribute()]
    public bool EditDateSpecified {
        get {
            return this.editDateFieldSpecified;
        }
        set {
            this.editDateFieldSpecified = value;
            this.RaisePropertyChanged("EditDateSpecified");
        }
    }

    // code omitted for brevity...
}

如您所见,除了为EditDate 生成一个支持属性外,还生成了一个额外的&lt;propertyname&gt;Specified 属性。一切都很好,除了当我执行以下操作时:

DataContractBase myDataContract = new DataContractBase();
myDataContract.EditDate = DateTime.Now;

new MyServiceClient.Update(new UpdateRequest(myDataContract));

EditDate 未被服务端点接收(未出现在传输的 XML 中)。

我调试了代码,发现虽然我设置了EditDate,但EditDateSpecified 属性并没有像我预期的那样设置为true;因此,XML 序列化程序忽略了 EditDate 的值,即使它设置为有效值。

作为一个小技巧,我修改了EditDate 属性,如下所示:

   /// <remarks/>
    [System.Xml.Serialization.XmlElementAttribute(Order=0)]
    public System.DateTime EditDate {
        get {
            return this.editDateField;
        }
        set {
            this.editDateField = value;

            // hackhackhack
            if (value != default(System.DateTime))
            {
              this.EditDateSpecified = true;
            }
            // end hackhackhack

            this.RaisePropertyChanged("EditDate");
        }
    }

现在代码按预期工作,但当然每次我重新生成代理时,我的修改都会丢失。我可以将调用代码更改为以下内容:

DataContractBase myDataContract = new DataContractBase();
myDataContract.EditDate = DateTime.Now;
myDataContract.EditDateSpecified = true;

new MyServiceClient.Update(new UpdateRequest(myDataContract));

但这似乎也有点浪费时间。

最后,我的问题是:是否有人对如何克服 Visual Studio 服务代理生成器的这种不直观(和 IMO 损坏)的行为提出建议,还是我只是遗漏了什么?

【问题讨论】:

  • [XMLSerializerFormat] 添加到您服务的属性中:选中此answer
  • 如果你从@MustafaMagdy 的评论中复制/粘贴——它是[XmlSerializerFormat](注意大小写)

标签: c# .net wcf


【解决方案1】:

这可能有点不直观(也让我措手不及!)——但它是处理 XML 模式中可能指定或未指定的元素的唯一正确方法。

而且您必须自己设置 xyzSpecified 标志似乎也违反直觉 - 但最终,这会给您更多的控制权,而 WCF 就是要非常明确和清楚地了解您的意图的 Four Tenets of SOA .

所以基本上 - 就是这样,习惯它 :-) 没有办法“过去”这种行为 - 这是 WCF 系统的设计方式,也是有充分理由的。

您始终可以做的是捕获并处理this.RaisePropertyChanged("EditDate"); 事件,并在该事件的事件处理程序中设置EditDateSpecified 标志。

【讨论】:

  • 当看起来有人忘记了 xyzSpecified 时生成警告是否违规?
  • 同意这是答案,不同意这是唯一的方法/好的设计。这是垃圾设计。他们需要提供一个选项,以便在简单用例的属性设置器中自动设置这些设置。
【解决方案2】:

试试这个

[DataMember(IsRequired=true)]
public DateTime EditDate { get; set; }

这应该省略EditDateSpecified 属性,因为该字段是按要求指定的

【讨论】:

  • 如果我们在本地代理中添加它,在构建客户端代码时不会被覆盖吗?
  • 你的类也需要有 [DataContract] 并且所有其他属性都需要有 [DataMember]。
【解决方案3】:

您可以使用扩展类来“自动指定”(绑定更改处理程序事件),而不是更改自动生成代码的设置器。这可能有两种实现——一个“惰性”实现(Autospecify)使用反射根据属性名称查找 fieldSpecified,而不是在某种开关中为每个类列出它们像Autonotify这样的声明:

懒惰

public static class PropertySpecifiedExtensions
{
    private const string SPECIFIED_SUFFIX = "Specified";

    /// <summary>
    /// Bind the <see cref="INotifyPropertyChanged.PropertyChanged"/> handler to automatically set any xxxSpecified fields when a property is changed.  "Lazy" via reflection.
    /// </summary>
    /// <param name="entity">the entity to bind the autospecify event to</param>
    /// <param name="specifiedSuffix">optionally specify a suffix for the Specified property to set as true on changes</param>
    /// <param name="specifiedPrefix">optionally specify a prefix for the Specified property to set as true on changes</param>
    public static void Autospecify(this INotifyPropertyChanged entity, string specifiedSuffix = SPECIFIED_SUFFIX, string specifiedPrefix = null)
    {
        entity.PropertyChanged += (me, e) =>
        {
            foreach (var pi in me.GetType().GetProperties().Where(o => o.Name == specifiedPrefix + e.PropertyName + specifiedSuffix))
            {
                pi.SetValue(me, true, BindingFlags.SetField | BindingFlags.SetProperty, null, null, null);
            }
        };
    }

    /// <summary>
    /// Create a new entity and <see cref="Autospecify"/> its properties when changed
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="specifiedSuffix"></param>
    /// <param name="specifiedPrefix"></param>
    /// <returns></returns>
    public static T Create<T>(string specifiedSuffix = SPECIFIED_SUFFIX, string specifiedPrefix = null) where T : INotifyPropertyChanged, new()
    {
        var ret = new T();
        ret.Autospecify(specifiedSuffix, specifiedPrefix);
        return ret;
    }
}

这简化了编写方便的工厂方法,例如:

public partial class MyRandomClass 
{
    /// <summary>
    /// Create a new empty instance and <see cref="PropertySpecifiedExtensions.Autospecify"/> its properties when changed
    /// </summary>
    /// <returns></returns>
    public static MyRandomClass Create()
    {
        return PropertySpecifiedExtensions.Create<MyRandomClass>();
    }
}

一个缺点(除了反射,meh)是您必须使用工厂方法来实例化您的类或使用.Autospecify之前(?)您使用说明符对属性进行任何更改。

无反射

如果你不喜欢反射,你可以定义另一个扩展类+接口:

public static class PropertySpecifiedExtensions2
{
    /// <summary>
    /// Bind the <see cref="INotifyPropertyChanged.PropertyChanged"/> handler to automatically call each class's <see cref="IAutoNotifyPropertyChanged.Autonotify"/> method on the property name.
    /// </summary>
    /// <param name="entity">the entity to bind the autospecify event to</param>
    public static void Autonotify(this IAutoNotifyPropertyChanged entity)
    {
        entity.PropertyChanged += (me, e) => ((IAutoNotifyPropertyChanged)me).WhenPropertyChanges(e.PropertyName);
    }

    /// <summary>
    /// Create a new entity and <see cref="Autonotify"/> it's properties when changed
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <returns></returns>
    public static T Create<T>() where T : IAutoNotifyPropertyChanged, new()
    {
        var ret = new T();
        ret.Autonotify();
        return ret;
    }
}

/// <summary>
/// Used by <see cref="PropertySpecifiedExtensions.Autonotify"/> to standardize implementation behavior
/// </summary>
public interface IAutoNotifyPropertyChanged : INotifyPropertyChanged
{
    void WhenPropertyChanges(string propertyName);
}

然后每个类自己定义行为:

public partial class MyRandomClass: IAutoNotifyPropertyChanged
{
    public void WhenPropertyChanges(string propertyName)
    {
        switch (propertyName)
        {
            case "field1": this.field1Specified = true; return;
            // etc
        }
    }
}

当然,这样做的缺点是属性名称的魔术字符串使重构变得困难,您可以通过 Expression 解析来解决?

【讨论】:

  • 嗨@drzaus 你知道他们为什么不设置“...FieldSpecified”值,如果他们已经开始引发“PropertyChanged”事件?例如,如果我有一个“MyProp”属性,并且在“set”块的末尾,他们正在提升“this.RaisePropertyChanged(“MyProp”);”,为什么他们不也设置“myPropFieldSpecified”值? ??
  • @RafaelEnriquez 谁是“他们”?微软?我猜您描述的根本问题与“他们”为服务代理生成提供的默认 T4 模板有关,但您的猜测与我的猜测一样好,为什么他们会这样做......
【解决方案4】:

伊恩, 请忽略我之前的答案,正在解释如何吸鸡蛋。我已经投票删除它们。

请告诉我您使用的是哪个版本的 Visual Studio?

在 VS2005 客户端中 - 在生成的代码中,我得到了 &lt;property&gt;Specified 标志,但没有在值更改时引发事件。要传递数据,我必须设置 &lt;property&gt;Specified 标志。

在 Visual Web Developer 2008 Express 客户端中 - 在生成的代码中,我没有收到 &lt;property&gt;Specified 标志,但我确实收到了值更改事件。

在我看来,此功能已经发展,Web Dev 2008 更接近您所追求的并且更直观,因为您在设置值后无需设置标志。

鲍蒂

【讨论】:

    【解决方案5】:

    这是一个简单的项目,可以在生成的 WCF 代码中修改可选属性的设置器,以便在设置相关值时自动将 *Specified 标志设置为 true。

    https://github.com/b9chris/WcfClean

    显然在某些情况下您希望手动控制 *Specified 标志,所以我不向所有人推荐它,但在大多数简单的用例中,*Specified 标志只是一个额外的麻烦,自动设置它们可以节省时间,并且通常更直观。

    请注意,如果您控制 Web 服务发布点,则此处另一个答案中的 Mustafa Magdy's comment 将为您解决此问题。但是,我通常不控制 Web 服务发布,只是使用一个,并且必须处理一些我希望自动化的简单软件中的 *Specified 标志。于是有了这个工具。

    【讨论】:

      【解决方案6】:

      更多信息

      在 MSDN 上here

      在她的回答中,Shreesha 解释说:

      “指定”字段仅在作为结构的可选参数上生成。 (int、datetime、decimal 等)。所有这些变量都会生成名为 Specified 的附加变量。

      这是一种了解参数是否真的在客户端和服务器之间传递的方法。

      更详细地说,一个可选整数,如果没有通过,仍然会有默认值 0。你如何区分这个和实际通过值 0 的整数? “指定”字段让您知道是否传递了可选整数。如果“指定”字段为假,则不传递该值。如果为真,则传递整数。

      所以本质上,设置这些字段的唯一方法是手动设置它们,如果它们没有为已设置的字段设置为 true,那么该字段将在 SOAP 消息中丢失网络服务调用。

      我最后做的是构建一个方法来循环遍历对象的所有成员,以及是否设置了属性,以及是否有一个名为 name _指定 然后将其设置为 true。

      【讨论】:

        【解决方案7】:

        将代理类属性更改为可空类型

        例如:

        布尔?确认

        日期时间?检查日期

        【讨论】:

        • 使用 WSDL 的元素可以同时具有 minOccurs="0" 和 nillable="true",因此您需要 FieldSpecified
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多