| File: Shared\Options\OptionBase.cs | Web Access |
| Project: src\src\dotnet-svcutil\lib\src\dotnet-svcutil-lib.csproj (dotnet-svcutil-lib) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using Newtonsoft.Json; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Globalization; using System.Linq; namespace Microsoft.Tools.ServiceModel.Svcutil { internal class OptionBase { public OptionBase(string name, params string[] aliases) : this(name, null, aliases) { } /// <summary> /// .ctr /// </summary> /// <param name="name">the option main name.</param> /// <param name="originalValue">Mainly used for initializaing options which value is a collection.</param> /// <param name="aliases">Used for identifying the option with different names, this allows for supporting different property names in the JSON schema.</param> public OptionBase(string name, object originalValue, params string[] aliases) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentNullException(nameof(name)); } this.Name = name; _serializationName = name; _value = originalValue; this.Aliases = new List<string>(); if (aliases != null && aliases.Length > 0) { this.Aliases.AddRange(aliases); } this.IsInitialized = true; } #region Properties and events // This property ensures an option is deserialized only once (in case it is defined multiple times in the JSON). internal bool IsInitialized { get; set; } // Aliases allows for deserializing a property that is referred to with a different name in the json file. public List<string> Aliases { get; private set; } public string Name { get; } private object _value; public object Value { get { return _value; } set { SetValue(value); } } // this is used in serialization to avoid serializing the option when unset. public virtual bool HasValue { get { return this.Value != null; } } // this is used in serialization to avoid serializing the option when it set to its default value. // obseve that this can be different from the CLR type's default value (Enum with a default different from the first value). public object DefaultValue { get; set; } // This allows for supporting a JSON schema with different property names (WCFCS vs SVCUTIL naming conventions). private string _serializationName; public string SerializationName { get { return _serializationName; } set { if (value != null && !Aliases.Contains(value)) Aliases.Add(value); _serializationName = value; } } // Determines whether the option can be serialized. // This can be a static value for options that should not be automatically serialized like the 'options' options. private bool _canSerialize = true; public virtual bool CanSerialize { get { return _canSerialize && this.Value != null && !this.Value.Equals(this.DefaultValue); } set { _canSerialize = value; } } // events public event EventHandler<OptionEventArgs> ValueChanging; public event EventHandler<OptionEventArgs> ValueChanged; public event EventHandler<OptionEventArgs> Serializing; public event EventHandler<OptionEventArgs> Serialized; public event EventHandler<OptionDeserializingEventArgs> Deserializing; public event EventHandler<EventArgs> Deserialized; #endregion public bool HasSameId(string optionId) { return StringComparer.OrdinalIgnoreCase.Compare(this.Name, optionId) == 0 || this.Aliases.Any(a => StringComparer.OrdinalIgnoreCase.Compare(a, optionId) == 0); } public virtual void CopyTo(OptionBase other) { // serialization name should not be copied as options may define different serialization names for their options. // other._serializationName = this._serializationName; // copy the canserialize field, not the property. The field contains a hardcoded value while the property may be computed dynamically! other._canSerialize = _canSerialize; other.Value = this.Value; other.DefaultValue = this.DefaultValue; other.ValueChanging = this.ValueChanging; other.ValueChanged = this.ValueChanged; other.Serializing = this.Serializing; other.Serialized = this.Serialized; other.Deserializing = this.Deserializing; other.Deserialized = this.Deserialized; } public OptionBase Clone() { var other = new OptionBase((string)this.Name, this.Aliases.ToArray()); CopyTo(other); return other; } protected virtual void SetValue(object value) { if (this.Value == null || !this.Value.Equals(value)) { var oldValue = this.Value; // notify event handlers (if any) to pre-process the value. value = OnValueChanging(value); _value = value; if (oldValue != _value) { OnValueChanged(oldValue); } } } internal void Deserialize(JToken jToken) { // Raise event for handlers to preprocess the JSON value. var value = OnDeserializing(jToken); // validate value; observe that null values are not allowed during deserialization. var stringValue = value as string; OptionValueParser.ThrowInvalidValueIf(value == null || (stringValue != null && string.IsNullOrWhiteSpace(stringValue)), jToken, this); _value = value; OnDeserialized(); } internal void Serialize(JsonWriter writer, JsonSerializer serializer) { if (this.CanSerialize) { // ensure the value is converted to a primitive type or a collection of primitive types // that can be safely serialized. var value = OptionValueParser.GetSerializationValue(this.Value); // notify event handlers (if any) to pre-process the JSON value. value = OnSerializing(value); serializer.Serialize(writer, value, value?.GetType()); OnSerialized(value); } } protected virtual object OnDeserializing(JToken jToken) { if (this.IsInitialized) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, Shared.Resources.ErrorOptionAlreadyDeserializedFormat, this.SerializationName)); } object value = null; if (Deserializing != null) { var e = new OptionDeserializingEventArgs(jToken); Deserializing(this, e); value = e.Value; } return value; } protected virtual void OnDeserialized() { this.IsInitialized = true; this.Deserialized?.Invoke(this, new EventArgs()); } protected virtual object OnSerializing(object value) { if (Serializing != null) { var e = new OptionEventArgs(value); Serializing(this, e); value = e.Value; } return value; } protected virtual void OnSerialized(object value) { // provide the actual serialized value. this.Serialized?.Invoke(this, new OptionEventArgs(value)); } protected virtual object OnValueChanging(object value) { if (this.IsInitialized && this.ValueChanging != null) { var eventArgs = new OptionEventArgs(value); this.ValueChanging.Invoke(this, eventArgs); value = eventArgs.Value; } return value; } protected virtual void OnValueChanged(object oldValue) { if (this.IsInitialized) { this.ValueChanged?.Invoke(this, new OptionEventArgs(oldValue)); } } public override string ToString() { var value = string.Format(CultureInfo.CurrentCulture, "{0}: {1}", this.Name, this.Value?.ToString()); return value; } } public class OptionEventArgs : EventArgs { public object Value { get; set; } public OptionEventArgs(object value) { Value = value; } } public class OptionDeserializingEventArgs : OptionEventArgs { public JToken JToken { get; set; } public OptionDeserializingEventArgs(JToken jToken) : base(null) { JToken = jToken; } } }