Провайдер службы WCF не устанавливает свойство "FieldSpecified"

У меня есть 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...
}

Как вы можете видеть, помимо создания свойства backing для EditDate, генерируется дополнительное свойство <propertyname>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));

но это также похоже на хакерскую трату времени.

Итак, наконец, мой вопрос: есть ли у кого-нибудь предложение о том, как преодолеть это неинтуитивное (и IMO-нарушение) поведение генератора прокси-сервера службы Visual Studio, или я просто что-то пропустил?

Ответ 1

Это может быть немного неинтуитивно (и поймал меня на страже и наматывался тоже!), но это единственный правильный способ обработки элементов, которые могут быть или не могут быть указаны в вашей XML-схеме.

И также может показаться противоречащим интуиции, что вы должны установить флаг xyzSpecified самостоятельно, но в конечном итоге это дает вам больше контроля, а WCF - это Четыре принципа SOA, чтобы быть ясными и ясными относительно ваших намерений.

Итак, в основном - так, как есть, привыкнуть к этому:-) Нет никакого способа "пройти" это поведение - это то, как была разработана система WCF, и по уважительной причине тоже.

То, что вы всегда можете сделать, это захватить и обработать событие this.RaisePropertyChanged("EditDate"); и установить флаг EditDateSpecified в обработчик события для этого события.

Ответ 2

попробуйте это

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

Это должно опускать свойство EditDateSpecified, так как поле указывается по мере необходимости

Ответ 3

Вместо того, чтобы изменять настройки автогенерированного кода, вы можете использовать класс расширения для "autospecify" (привязать событие обработчика изменений). Это может иметь две реализации: "ленивый" (Autospecify) с использованием отражения для поиска fieldSpecified на основе имени свойства, а не для перечисления их всех для каждого класса в каком-то выражении switch, например 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;
    }
}

Это упрощает удобство записи factory, например:

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) заключается в том, что вы должны использовать метод factory для создания экземпляров ваших классов или использовать .Autospecify раньше (?) вы вносите какие-либо изменения в свойства со спецификаторами.

Без отражения

Если вам не нравится отражение, вы можете определить другой класс расширения + интерфейс:

public static class PropertySpecifiedExtensions2
{
    /// <summary>
    /// Bind the <see cref="INotifyPropertyChanged.PropertyChanged"/> handler to automatically call each class <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 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 parsing?

Ответ 4

Ян, Пожалуйста, проигнорируйте мои предыдущие ответы, объяснял, как сосать яйца. Я проголосовал за их удаление.

Не могли бы вы рассказать мне, какую версию Visual Studio вы используете, пожалуйста?

В VS2005-клиенте - в сгенерированном коде я получаю флаги <property>Specified, но при изменении значений события не возникает. Для передачи данных я должен установить флаг <property>Specified.

В Visual Web Developer 2008 Express клиент - в сгенерированном коде я не получаю флаги <property>Specified, но я получаю событие при изменении значения.

Мне кажется, что эта функциональность эволюционировала, и Web Dev 2008 ближе к тому, что вам нужно, и более интуитивно понятен, поскольку вам не нужно устанавливать флаги, как только вы установили значение.

Bowthy

Ответ 5

Вот простой проект, который может модифицировать сеттеры в сгенерированном коде WCF для дополнительных свойств, чтобы автоматически устанавливать флаги * Specified в true при установке соответствующего значения.

https://github.com/b9chris/WcfClean

Очевидно, что есть ситуации, когда вы хотите вручную управлять флагом * Specified, поэтому я не рекомендую его всем, но в большинстве простых случаев использование флагов * Указаны только дополнительные неприятности, и автоматическая установка их экономит время и часто более интуитивно понятны.

Обратите внимание, что комментарий Mustafa Magdy на другой ответ здесь разрешит это для вас, если вы будете контролировать точку публикации веб-службы. Тем не менее, я обычно не контролирую публикацию веб-службы и просто ее потребляю, и мне приходится справляться с * указанными флагами в каком-то простом программном обеспечении, где я бы хотел, чтобы это автоматизировалось. Таким образом, этот инструмент.

Ответ 6

Дополнительная информация

На MSDN здесь

В своем ответе Ширше объясняет, что:

"Указанные" поля генерируются только по необязательным параметрам, которые являются структурами. (int, datetime, decimal и т.д.). Все такие переменные будут иметь дополнительную переменную, сгенерированную с именем Specified.

Это способ узнать, действительно ли передан параметр между клиентом и сервером.

Чтобы уточнить, необязательное целое число, если оно не передано, все равно будет иметь значение dafault 0. Как вы делаете различие между этим и тем, который был фактически передан со значением 0? Поле "указанное" позволяет узнать, прошло ли опциональное целое число или нет. Если "указанное" поле ложно, значение не передается. Если оно истинно, передается целое число.

по сути, единственный способ установить эти поля - установить их вручную, и если они не установлены в true для поля, которое было установлено, то это поле будет пропущено в SOAP-сообщении вызов веб-сервиса.

В конце концов я создал метод для циклического перемещения всех элементов объекта, и если свойство было установлено, и если есть свойство с именем name _Specified, то установите, что к true.

Ответ 7

Измените свойства класса прокси на тип с нулевым значением

ex:

BOOL? подтверждена

DateTime? checkDate