diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b4f7de..dccc62a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,14 @@ These are the changes to each version that has been released on the official [Visual Studio extension gallery](https://marketplace.visualstudio.com/items?itemName=unchase.UnchaseODataConnectedService). +## v1.3.1 `2019-09-21` + +- [x] Add support for emitting dynamic properties on open types for `OData V4` + ## v 1.3.0 `2019-09-21` - [x] Close the [issue #5](https://github.com/unchase/Unchase.Odata.Connectedservice/issues/5): - - [x] Add support for loading client code generation parameters from json files (including `Connected Service .json` from OData Connected Service by Microsoft) + - [x] Add support for loading client code generation parameters from json files (including `Connected Service .json` from `OData Connected Service` by Microsoft) - [x] Fix bug with multiple untrusted sertificate validation (if multiple OData services are hosted on one endpoint) - [x] Add more code generation parameters to stored `UserSettings` - [x] Add `Excluded Operation imports names` to OData V4 generation template for excluding `OperationImports` you need diff --git a/README.md b/README.md index 9976991..a878287 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,9 @@ Install from `Tools -> Extensions and Updates` menu inside [Visual Studio](https ## Features - Generate `C#` and `VB` clients/proxies (client code) from OData specifications for OData protocol versions 1.0-4.0 +- Support for emitting dynamic properties on open types for OData protocol version 4.0 +- Support selection `OperationImports` (`Actions` and `FunctionImports`) that will be generated for OData protocol versions 1.0-4.0 +- Support loading client code generation parameters from json files (including `Connected Service .json` from OData Connected Service by Microsoft) - Generate `C#` proxy-class extension methods for OData protocol versions 1.0-3.0 - Generate functions to call service methods from OData protocol versions 1.0-3.0 `FunctionImports` or from OData protocol version 4.0 `OperationImports` - You can select the necessary methods that will be added after generation for OData protocol versions 1.0-4.0 diff --git a/src/Unchase.OData.ConnectedService/CodeGeneration/V4CodeGenDescriptor.cs b/src/Unchase.OData.ConnectedService/CodeGeneration/V4CodeGenDescriptor.cs index 3b51b01..48c16df 100644 --- a/src/Unchase.OData.ConnectedService/CodeGeneration/V4CodeGenDescriptor.cs +++ b/src/Unchase.OData.ConnectedService/CodeGeneration/V4CodeGenDescriptor.cs @@ -104,6 +104,9 @@ private async Task AddT4FileAsync() text = Regex.Replace(text, "(public const bool EnableNamingAlias = )true;", "$1" + this.ServiceConfiguration.EnableNamingAlias.ToString().ToLower(CultureInfo.InvariantCulture) + ";"); text = Regex.Replace(text, "(public const bool IgnoreUnexpectedElementsAndAttributes = )true;", "$1" + this.ServiceConfiguration.IgnoreUnexpectedElementsAndAttributes.ToString().ToLower(CultureInfo.InvariantCulture) + ";"); + text = Regex.Replace(text, "(public const bool GenerateDynamicPropertiesCollection = )true;", "$1" + this.ServiceConfiguration.GenerateDynamicPropertiesCollection.ToString().ToLower(CultureInfo.InvariantCulture) + ";"); + text = Regex.Replace(text, "(public const string DynamicPropertiesCollectionName = )\"DynamicProperties\";", "$1\"" + $"{(!string.IsNullOrWhiteSpace(ServiceConfiguration.DynamicPropertiesCollectionName) ? ServiceConfiguration.DynamicPropertiesCollectionName : Common.Constants.DefaultDynamicPropertiesCollectionName)}" + "\";"); + text = Regex.Replace(text, "(public const string ExcludedOperationImportsNames = )\"\";", "$1\"" + this.ServiceConfiguration.ExcludedOperationImportsNames + "\";"); await writer.WriteAsync(text); @@ -142,7 +145,9 @@ private async Task AddGeneratedCodeAsync() IgnoreUnexpectedElementsAndAttributes = this.ServiceConfiguration.IgnoreUnexpectedElementsAndAttributes, EnableNamingAlias = this.ServiceConfiguration.EnableNamingAlias, NamespacePrefix = this.ServiceConfiguration.NamespacePrefix, - ExcludedOperationImportsNames = this.ServiceConfiguration?.ExcludedOperationImportsNames + ExcludedOperationImportsNames = this.ServiceConfiguration?.ExcludedOperationImportsNames, + GenerateDynamicPropertiesCollection = this.ServiceConfiguration.GenerateDynamicPropertiesCollection, + DynamicPropertiesCollectionName = this.ServiceConfiguration?.DynamicPropertiesCollectionName }; var tempFile = Path.GetTempFileName(); diff --git a/src/Unchase.OData.ConnectedService/Common/Constants.cs b/src/Unchase.OData.ConnectedService/Common/Constants.cs index fa391b9..5f66afa 100644 --- a/src/Unchase.OData.ConnectedService/Common/Constants.cs +++ b/src/Unchase.OData.ConnectedService/Common/Constants.cs @@ -21,6 +21,7 @@ internal static class Constants public const string DefaultReferenceFileName = "Reference"; public const string DefaultNamespacePrefix = "Reference"; public const string DefaultServiceName = "OData Service"; + public const string DefaultDynamicPropertiesCollectionName = "DynamicProperties"; public static Version EdmxVersion1 = new Version(1, 0, 0, 0); public static Version EdmxVersion2 = new Version(2, 0, 0, 0); diff --git a/src/Unchase.OData.ConnectedService/Models/ServiceConfiguration.cs b/src/Unchase.OData.ConnectedService/Models/ServiceConfiguration.cs index 67457bd..e8564b4 100644 --- a/src/Unchase.OData.ConnectedService/Models/ServiceConfiguration.cs +++ b/src/Unchase.OData.ConnectedService/Models/ServiceConfiguration.cs @@ -70,6 +70,10 @@ internal class ServiceConfigurationV4 : ServiceConfigurationV3 public bool IgnoreUnexpectedElementsAndAttributes { get; set; } + public bool GenerateDynamicPropertiesCollection { get; set; } + + public string DynamicPropertiesCollectionName { get; set; } + public bool IncludeT4File { get; set; } } #endregion diff --git a/src/Unchase.OData.ConnectedService/Models/UserSettings.cs b/src/Unchase.OData.ConnectedService/Models/UserSettings.cs index cefb88d..5f2e312 100644 --- a/src/Unchase.OData.ConnectedService/Models/UserSettings.cs +++ b/src/Unchase.OData.ConnectedService/Models/UserSettings.cs @@ -56,6 +56,12 @@ internal class UserSettings [DataMember] public bool IgnoreUnexpectedElementsAndAttributes { get; set; } + [DataMember] + public bool GenerateDynamicPropertiesCollection { get; set; } = true; + + [DataMember] + public string DynamicPropertiesCollectionName { get; set; } = Constants.DefaultDynamicPropertiesCollectionName; + [DataMember] public bool IncludeT4File { get; set; } diff --git a/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.cs b/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.cs index 89e3a2b..95ae5c4 100644 --- a/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.cs +++ b/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.cs @@ -40,6 +40,7 @@ public virtual string TransformText() /* OData Client T4 Template ver. #VersionNumber# Copyright (c) Microsoft Corporation + Updated by Unchase (https://github.com/unchase) All rights reserved. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -61,6 +62,8 @@ The above copyright notice and this permission notice shall be included in all c EnableNamingAlias = this.EnableNamingAlias, TempFilePath = this.TempFilePath, IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes, + GenerateDynamicPropertiesCollection = this.GenerateDynamicPropertiesCollection, + DynamicPropertiesCollectionName = this.DynamicPropertiesCollectionName, ExcludedOperationImportsNames = this.ExcludedOperationImportsNames }; } @@ -79,6 +82,8 @@ The above copyright notice and this permission notice shall be included in all c EnableNamingAlias = this.EnableNamingAlias, TempFilePath = this.TempFilePath, IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes, + GenerateDynamicPropertiesCollection = this.GenerateDynamicPropertiesCollection, + DynamicPropertiesCollectionName = this.DynamicPropertiesCollectionName, ExcludedOperationImportsNames = this.ExcludedOperationImportsNames }; } @@ -155,11 +160,21 @@ public static class Configuration // the client code if any. The value must be set to true or false. public const bool IgnoreUnexpectedElementsAndAttributes = true; + // This flag indicates whether to generate an additional Dictionary property + // to store dynamic properties in open types. The value must be set to true or false. + public const bool GenerateDynamicPropertiesCollection = true; + + // This defines the name of Dictionary properties + // that store dynamic properties in open types. This property is only applicable if + // GenerateDynamicPropertiesCollection is set to true. This value must be + // a valid identifier name. + public const string DynamicPropertiesCollectionName = "DynamicProperties"; + // The string for the comma separated OperationImports (ActionImports and FunctionImports) names in metadata to exclude from generated code. public const string ExcludedOperationImportsNames = ""; // - public const string T4Version = "2.5.0"; + public const string T4Version = "2.6.0"; } public static class Customization @@ -324,6 +339,24 @@ public bool IgnoreUnexpectedElementsAndAttributes set; } +/// +/// true to generate open type property dirctionary, false otherwise. +/// +public bool GenerateDynamicPropertiesCollection +{ + get; + set; +} + +/// +/// Name of the OpenType dictionary property +/// +public string DynamicPropertiesCollectionName +{ + get; + set; +} + /// /// The string for the comma separated OperationImports (ActionImports and FunctionImports) names in metadata to exclude from generated code. /// @@ -428,6 +461,26 @@ public void ValidateAndSetIgnoreUnexpectedElementsAndAttributesFromString(string this.IgnoreUnexpectedElementsAndAttributes = boolValue; } +/// +/// Set the GenerateDynamicPropertiesCollection property with the given value. +/// +/// The value to set. +public void ValidateAndSetGenerateDynamicPropertiesCollectionFromString(string stringValue) +{ + bool boolValue; + if (!bool.TryParse(stringValue, out boolValue)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" cannot be assigned to the GenerateDynamicPropertiesCollection parameter because it is not a valid boolean value.", stringValue)); + } + + this.GenerateDynamicPropertiesCollection = boolValue; +} + /// /// Reads the parameter values from the Configuration class and applies them. /// @@ -440,6 +493,8 @@ private void ApplyParametersFromConfigurationClass() this.EnableNamingAlias = Configuration.EnableNamingAlias; this.TempFilePath = Configuration.TempFilePath; this.IgnoreUnexpectedElementsAndAttributes = Configuration.IgnoreUnexpectedElementsAndAttributes; + this.GenerateDynamicPropertiesCollection = Configuration.GenerateDynamicPropertiesCollection; + this.DynamicPropertiesCollectionName = Configuration.DynamicPropertiesCollectionName; this.ExcludedOperationImportsNames = Configuration.ExcludedOperationImportsNames; } @@ -489,8 +544,20 @@ private void ApplyParametersFromCommandLine() this.ValidateAndSetIgnoreUnexpectedElementsAndAttributesFromString(ignoreUnexpectedElementsAndAttributes); } + string generateDynamicPropertiesCollection = this.Host.ResolveParameterValue("notempty", "notempty", "GenerateDynamicPropertiesCollection"); + if (!string.IsNullOrEmpty(generateDynamicPropertiesCollection)) + { + this.ValidateAndSetGenerateDynamicPropertiesCollectionFromString(generateDynamicPropertiesCollection); + } + + string dynamicPropertiesCollectionName = this.Host.ResolveParameterValue("notempty", "notempty", "DynamicPropertiesCollectionName"); + if (!string.IsNullOrEmpty(dynamicPropertiesCollectionName)) + { + this.DynamicPropertiesCollectionName = dynamicPropertiesCollectionName; + } + string excludedOperationImportsNames = this.Host.ResolveParameterValue("notempty", "notempty", "ExcludedOperationImportsNames"); - if (!string.IsNullOrEmpty(namespacePrefix)) + if (!string.IsNullOrEmpty(excludedOperationImportsNames)) { this.ExcludedOperationImportsNames = excludedOperationImportsNames; } @@ -828,6 +895,24 @@ public bool IgnoreUnexpectedElementsAndAttributes set; } + /// + /// true to generate open type property dirctionary, false otherwise. + /// + public bool GenerateDynamicPropertiesCollection + { + get; + set; + } + + /// + /// Name of the OpenType dictionary property + /// + public string DynamicPropertiesCollectionName + { + get; + set; + } + /// /// The string for the comma separated OperationImports (ActionImports and FunctionImports) names in metadata to exclude from generated code. /// @@ -1086,6 +1171,7 @@ public ODataClientTemplate(CodeGenerationContext context) internal abstract string NewModifier { get; } internal abstract string GeoTypeInitializePattern { get; } internal abstract string Int32TypeName { get; } + internal abstract string ObjectTypeName { get; } internal abstract string StringTypeName { get; } internal abstract string BinaryTypeName { get; } internal abstract string DecimalTypeName { get; } @@ -1120,6 +1206,7 @@ public ODataClientTemplate(CodeGenerationContext context) internal abstract string TimeOfDayTypeName { get; } internal abstract string XmlConvertClassName { get; } internal abstract string EnumTypeName { get; } + internal abstract string DictionaryTypeName { get; } internal abstract HashSet LanguageKeywords { get; } internal abstract string FixPattern { get; } internal abstract string EnumUnderlyingTypeMarker { get; } @@ -1128,6 +1215,7 @@ public ODataClientTemplate(CodeGenerationContext context) internal abstract string UriOperationParameterConstructor { get; } internal abstract string UriEntityOperationParameterConstructor { get; } internal abstract string BodyOperationParameterConstructor { get; } + internal abstract string DictionaryConstructor { get; } internal abstract string BaseEntityType { get; } internal abstract string OverloadsModifier { get; } internal abstract string ODataVersion { get; } @@ -1842,7 +1930,7 @@ internal void WriteEntityType(IEdmEntityType entityType, Dictionary properties) + internal void WritePropertiesForStructuredType(IEnumerable properties, bool isOpen) { bool useDataServiceCollection = this.context.UseDataServiceCollection; @@ -2314,6 +2402,19 @@ internal void WritePropertiesForStructuredType(IEnumerable propert }; }).ToList(); + if (isOpen && this.context.GenerateDynamicPropertiesCollection) + { + propertyInfos.Add(new + { + PropertyType = string.Format(this.DictionaryTypeName, this.StringTypeName, this.ObjectTypeName), + PropertyVanillaName = string.Empty, // No such property in metadata + PropertyName = this.context.DynamicPropertiesCollectionName, + FixedPropertyName = GetFixedName(this.context.DynamicPropertiesCollectionName), + PrivatePropertyName = "_" + Utils.CamelCase(this.context.DynamicPropertiesCollectionName), + PropertyInitializationValue = string.Format(this.DictionaryConstructor, this.StringTypeName, this.ObjectTypeName) + }); + } + // Private name should not confict with field name UniqueIdentifierService uniqueIdentifierService = new UniqueIdentifierService(propertyInfos.Select(_ => _.FixedPropertyName), this.context.TargetLanguage == LanguageOption.CSharp); @@ -3217,6 +3318,7 @@ public ODataClientCSharpTemplate(CodeGenerationContext context) internal override string NewModifier => "new "; internal override string GeoTypeInitializePattern => "global::Microsoft.Spatial.SpatialImplementation.CurrentImplementation.CreateWellKnownTextSqlFormatter(false).Read<{0}>(new global::System.IO.StringReader(\"{1}\"))"; internal override string Int32TypeName => "int"; + internal override string ObjectTypeName => "object"; internal override string StringTypeName => "string"; internal override string BinaryTypeName => "byte[]"; internal override string DecimalTypeName => "decimal"; @@ -3251,6 +3353,7 @@ public ODataClientCSharpTemplate(CodeGenerationContext context) internal override string TimeOfDayTypeName => "global::Microsoft.OData.Edm.TimeOfDay"; internal override string XmlConvertClassName => "global::System.Xml.XmlConvert"; internal override string EnumTypeName => "global::System.Enum"; + internal override string DictionaryTypeName => "global::System.Collections.Generic.Dictionary<{0}, {1}>"; internal override string FixPattern => "@{0}"; internal override string EnumUnderlyingTypeMarker => " : "; internal override string ConstantExpressionConstructorWithType => "global::System.Linq.Expressions.Expression.Constant({0}, typeof({1}))"; @@ -3258,6 +3361,7 @@ public ODataClientCSharpTemplate(CodeGenerationContext context) internal override string UriOperationParameterConstructor => "new global::Microsoft.OData.Client.UriOperationParameter(\"{0}\", {1})"; internal override string UriEntityOperationParameterConstructor => "new global::Microsoft.OData.Client.UriEntityOperationParameter(\"{0}\", {1}, {2})"; internal override string BodyOperationParameterConstructor => "new global::Microsoft.OData.Client.BodyOperationParameter(\"{0}\", {1})"; + internal override string DictionaryConstructor => $"new {DictionaryTypeName}()"; internal override string BaseEntityType => " : global::Microsoft.OData.Client.BaseEntityType"; internal override string OverloadsModifier => "new "; internal override string ODataVersion => "global::Microsoft.OData.ODataVersion.V4"; @@ -5256,6 +5360,7 @@ public ODataClientVBTemplate(CodeGenerationContext context) internal override string NewModifier { get { return "New "; } } internal override string GeoTypeInitializePattern { get { return "Global.Microsoft.Spatial.SpatialImplementation.CurrentImplementation.CreateWellKnownTextSqlFormatter(False).Read(Of {0})(New Global.System.IO.StringReader(\"{1}\"))"; } } internal override string Int32TypeName { get { return "Integer"; } } + internal override string ObjectTypeName { get { return "Object"; } } internal override string StringTypeName { get { return "String"; } } internal override string BinaryTypeName { get { return "Byte()"; } } internal override string DecimalTypeName { get { return "Decimal"; } } @@ -5290,6 +5395,7 @@ public ODataClientVBTemplate(CodeGenerationContext context) internal override string TimeOfDayTypeName { get { return "Global.Microsoft.OData.Edm.TimeOfDay"; } } internal override string XmlConvertClassName { get { return "Global.System.Xml.XmlConvert"; } } internal override string EnumTypeName { get { return "Global.System.Enum"; } } + internal override string DictionaryTypeName { get { return "Global.System.Collections.Generic.Dictionary(Of {0}, {1})"; } } internal override string FixPattern { get { return "[{0}]"; } } internal override string EnumUnderlyingTypeMarker { get { return " As "; } } internal override string ConstantExpressionConstructorWithType { get { return "Global.System.Linq.Expressions.Expression.Constant({0}, GetType({1}))"; } } @@ -5297,6 +5403,7 @@ public ODataClientVBTemplate(CodeGenerationContext context) internal override string UriOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.UriOperationParameter(\"{0}\", {1})"; } } internal override string UriEntityOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.UriEntityOperationParameter(\"{0}\", {1}, {2})"; } } internal override string BodyOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.BodyOperationParameter(\"{0}\", {1})"; } } + internal override string DictionaryConstructor { get { return $"New {DictionaryTypeName}"; } } internal override string BaseEntityType { get { return "\r\n Inherits Global.Microsoft.OData.Client.BaseEntityType"; } } internal override string OverloadsModifier { get { return "Overloads "; } } internal override string ODataVersion { get { return "Global.Microsoft.OData.ODataVersion.V4"; } } diff --git a/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.tt b/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.tt index c5b0e57..578ecdb 100644 --- a/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.tt +++ b/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.tt @@ -29,11 +29,21 @@ public static class Configuration // the client code if any. The value must be set to true or false. public const bool IgnoreUnexpectedElementsAndAttributes = true; + // This flag indicates whether to generate an additional Dictionary property + // to store dynamic properties in open types. The value must be set to true or false. + public const bool GenerateDynamicPropertiesCollection = true; + + // This defines the name of Dictionary properties + // that store dynamic properties in open types. This property is only applicable if + // GenerateDynamicPropertiesCollection is set to true. This value must be + // a valid identifier name. + public const string DynamicPropertiesCollectionName = "DynamicProperties"; + // The string for the comma separated OperationImports (ActionImports and FunctionImports) names in metadata to exclude from generated code. public const string ExcludedOperationImportsNames = ""; // - public const string T4Version = "2.5.0"; + public const string T4Version = "2.6.0"; } public static class Customization diff --git a/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.ttinclude b/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.ttinclude index 5d73ea1..c7dd2aa 100644 --- a/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.ttinclude +++ b/src/Unchase.OData.ConnectedService/Templates/ODataT4CodeGenerator.ttinclude @@ -2,6 +2,7 @@ /* OData Client T4 Template ver. #VersionNumber# Copyright (c) Microsoft Corporation +Updated by Unchase (https://github.com/unchase) All rights reserved. MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: @@ -52,6 +53,8 @@ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI EnableNamingAlias = this.EnableNamingAlias, TempFilePath = this.TempFilePath, IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes, + GenerateDynamicPropertiesCollection = this.GenerateDynamicPropertiesCollection, + DynamicPropertiesCollectionName = this.DynamicPropertiesCollectionName, ExcludedOperationImportsNames = this.ExcludedOperationImportsNames }; } @@ -70,6 +73,8 @@ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI EnableNamingAlias = this.EnableNamingAlias, TempFilePath = this.TempFilePath, IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes, + GenerateDynamicPropertiesCollection = this.GenerateDynamicPropertiesCollection, + DynamicPropertiesCollectionName = this.DynamicPropertiesCollectionName, ExcludedOperationImportsNames = this.ExcludedOperationImportsNames }; } @@ -235,6 +240,24 @@ public bool IgnoreUnexpectedElementsAndAttributes set; } +/// +/// true to generate open type property dirctionary, false otherwise. +/// +public bool GenerateDynamicPropertiesCollection +{ + get; + set; +} + +/// +/// Name of the OpenType dictionary property. +/// +public string DynamicPropertiesCollectionName +{ + get; + set; +} + /// /// The string for the comma separated OperationImports (ActionImports and FunctionImports) names in metadata to exclude from generated code. /// @@ -339,6 +362,26 @@ public void ValidateAndSetIgnoreUnexpectedElementsAndAttributesFromString(string this.IgnoreUnexpectedElementsAndAttributes = boolValue; } +/// +/// Set the GenerateDynamicPropertiesCollection property with the given value. +/// +/// The value to set. +public void ValidateAndSetGenerateDynamicPropertiesCollectionFromString(string stringValue) +{ + bool boolValue; + if (!bool.TryParse(stringValue, out boolValue)) + { + // ******************************************************************************************************** + // To fix this error, if the current text transformation is run by the TextTemplatingFileGenerator + // custom tool inside Visual Studio, update the .odata.config file in the project with a valid parameter + // value then hit Ctrl-S to save the .tt file to refresh the code generation. + // ******************************************************************************************************** + throw new ArgumentException(string.Format("The value \"{0}\" cannot be assigned to the GenerateDynamicPropertiesCollection parameter because it is not a valid boolean value.", stringValue)); + } + + this.GenerateDynamicPropertiesCollection = boolValue; +} + /// /// Reads the parameter values from the Configuration class and applies them. /// @@ -351,6 +394,8 @@ private void ApplyParametersFromConfigurationClass() this.EnableNamingAlias = Configuration.EnableNamingAlias; this.TempFilePath = Configuration.TempFilePath; this.IgnoreUnexpectedElementsAndAttributes = Configuration.IgnoreUnexpectedElementsAndAttributes; + this.GenerateDynamicPropertiesCollection = Configuration.GenerateDynamicPropertiesCollection; + this.DynamicPropertiesCollectionName = Configuration.DynamicPropertiesCollectionName; this.ExcludedOperationImportsNames = Configuration.ExcludedOperationImportsNames; } @@ -400,8 +445,20 @@ private void ApplyParametersFromCommandLine() this.ValidateAndSetIgnoreUnexpectedElementsAndAttributesFromString(ignoreUnexpectedElementsAndAttributes); } + string generateDynamicPropertiesCollection = this.Host.ResolveParameterValue("notempty", "notempty", "GenerateDynamicPropertiesCollection"); + if (!string.IsNullOrEmpty(generateDynamicPropertiesCollection)) + { + this.ValidateAndSetGenerateDynamicPropertiesCollectionFromString(generateDynamicPropertiesCollection); + } + + string dynamicPropertiesCollectionName = this.Host.ResolveParameterValue("notempty", "notempty", "DynamicPropertiesCollectionName"); + if (!string.IsNullOrEmpty(dynamicPropertiesCollectionName)) + { + this.DynamicPropertiesCollectionName = dynamicPropertiesCollectionName; + } + string excludedOperationImportsNames = this.Host.ResolveParameterValue("notempty", "notempty", "ExcludedOperationImportsNames"); - if (!string.IsNullOrEmpty(namespacePrefix)) + if (!string.IsNullOrEmpty(excludedOperationImportsNames)) { this.ExcludedOperationImportsNames = excludedOperationImportsNames; } @@ -746,6 +803,24 @@ public class CodeGenerationContext /// true to ignore unknown elements or attributes in metadata, false otherwise. /// public bool IgnoreUnexpectedElementsAndAttributes + { + get; + set; + } + + /// + /// true to generate open type property dirctionary, false otherwise. + /// + public bool GenerateDynamicPropertiesCollection + { + get; + set; + } + + /// + /// Name of the OpenType dictionary property. + /// + public string DynamicPropertiesCollectionName { get; set; @@ -1021,6 +1096,7 @@ public abstract class ODataClientTemplate : TemplateBase internal abstract string NewModifier { get; } internal abstract string GeoTypeInitializePattern { get; } internal abstract string Int32TypeName { get; } + internal abstract string ObjectTypeName { get; } internal abstract string StringTypeName { get; } internal abstract string BinaryTypeName { get; } internal abstract string DecimalTypeName { get; } @@ -1055,6 +1131,7 @@ public abstract class ODataClientTemplate : TemplateBase internal abstract string TimeOfDayTypeName { get; } internal abstract string XmlConvertClassName { get; } internal abstract string EnumTypeName { get; } + internal abstract string DictionaryTypeName { get; } internal abstract HashSet LanguageKeywords { get; } internal abstract string FixPattern { get; } internal abstract string EnumUnderlyingTypeMarker { get; } @@ -1063,6 +1140,7 @@ public abstract class ODataClientTemplate : TemplateBase internal abstract string UriOperationParameterConstructor { get; } internal abstract string UriEntityOperationParameterConstructor { get; } internal abstract string BodyOperationParameterConstructor { get; } + internal abstract string DictionaryConstructor { get; } internal abstract string BaseEntityType { get; } internal abstract string OverloadsModifier { get; } internal abstract string ODataVersion { get; } @@ -1803,7 +1881,7 @@ public abstract class ODataClientTemplate : TemplateBase this.WriteStructurdTypeDeclaration(entityType, this.BaseEntityType); this.SetPropertyIdentifierMappingsIfNameConflicts(entityType.Name, entityType); this.WriteTypeStaticCreateMethod(entityType.Name, entityType); - this.WritePropertiesForStructuredType(entityType.DeclaredProperties); + this.WritePropertiesForStructuredType(entityType.DeclaredProperties, entityType.IsOpen); if (entityType.BaseType == null && this.context.UseDataServiceCollection) { @@ -1821,7 +1899,7 @@ public abstract class ODataClientTemplate : TemplateBase this.WriteStructurdTypeDeclaration(complexType, string.Empty); this.SetPropertyIdentifierMappingsIfNameConflicts(complexType.Name, complexType); this.WriteTypeStaticCreateMethod(complexType.Name, complexType); - this.WritePropertiesForStructuredType(complexType.DeclaredProperties); + this.WritePropertiesForStructuredType(complexType.DeclaredProperties, complexType.IsOpen); if (complexType.BaseType == null && this.context.UseDataServiceCollection) { @@ -2260,7 +2338,7 @@ public abstract class ODataClientTemplate : TemplateBase } } - internal void WritePropertiesForStructuredType(IEnumerable properties) + internal void WritePropertiesForStructuredType(IEnumerable properties, bool isOpen) { bool useDataServiceCollection = this.context.UseDataServiceCollection; @@ -2280,6 +2358,19 @@ public abstract class ODataClientTemplate : TemplateBase }; }).ToList(); + if(isOpen && this.context.GenerateDynamicPropertiesCollection) + { + propertyInfos.Add(new + { + PropertyType = string.Format(this.DictionaryTypeName, this.StringTypeName, this.ObjectTypeName), + PropertyVanillaName = string.Empty, // No such property in metadata + PropertyName = this.context.DynamicPropertiesCollectionName, + FixedPropertyName = GetFixedName(this.context.DynamicPropertiesCollectionName), + PrivatePropertyName = "_" + Utils.CamelCase(this.context.DynamicPropertiesCollectionName), + PropertyInitializationValue = string.Format(this.DictionaryConstructor, this.StringTypeName, this.ObjectTypeName) + }); + } + // Private name should not confict with field name UniqueIdentifierService uniqueIdentifierService = new UniqueIdentifierService(propertyInfos.Select(_ => _.FixedPropertyName), this.context.TargetLanguage == LanguageOption.CSharp); @@ -3207,6 +3298,7 @@ public sealed class ODataClientCSharpTemplate : ODataClientTemplate internal override string NewModifier { get { return "new "; } } internal override string GeoTypeInitializePattern { get { return "global::Microsoft.Spatial.SpatialImplementation.CurrentImplementation.CreateWellKnownTextSqlFormatter(false).Read<{0}>(new global::System.IO.StringReader(\"{1}\"))"; } } internal override string Int32TypeName { get { return "int"; } } + internal override string ObjectTypeName { get { return "object"; } } internal override string StringTypeName { get { return "string"; } } internal override string BinaryTypeName { get { return "byte[]"; } } internal override string DecimalTypeName { get { return "decimal"; } } @@ -3241,6 +3333,7 @@ public sealed class ODataClientCSharpTemplate : ODataClientTemplate internal override string TimeOfDayTypeName { get { return "global::Microsoft.OData.Edm.TimeOfDay"; } } internal override string XmlConvertClassName { get { return "global::System.Xml.XmlConvert"; } } internal override string EnumTypeName { get { return "global::System.Enum"; } } + internal override string DictionaryTypeName { get { return "global::System.Collections.Generic.Dictionary<{0}, {1}>"; } } internal override string FixPattern { get { return "@{0}"; } } internal override string EnumUnderlyingTypeMarker { get { return " : "; } } internal override string ConstantExpressionConstructorWithType { get { return "global::System.Linq.Expressions.Expression.Constant({0}, typeof({1}))"; } } @@ -3248,6 +3341,7 @@ public sealed class ODataClientCSharpTemplate : ODataClientTemplate internal override string UriOperationParameterConstructor { get { return "new global::Microsoft.OData.Client.UriOperationParameter(\"{0}\", {1})"; } } internal override string UriEntityOperationParameterConstructor { get { return "new global::Microsoft.OData.Client.UriEntityOperationParameter(\"{0}\", {1}, {2})"; } } internal override string BodyOperationParameterConstructor { get { return "new global::Microsoft.OData.Client.BodyOperationParameter(\"{0}\", {1})"; } } + internal override string DictionaryConstructor { get { return $"new {DictionaryTypeName}()"; } } internal override string BaseEntityType { get { return " : global::Microsoft.OData.Client.BaseEntityType"; } } internal override string OverloadsModifier { get { return "new "; } } internal override string ODataVersion { get { return "global::Microsoft.OData.ODataVersion.V4"; } } @@ -4324,6 +4418,7 @@ public sealed class ODataClientVBTemplate : ODataClientTemplate internal override string NewModifier { get { return "New "; } } internal override string GeoTypeInitializePattern { get { return "Global.Microsoft.Spatial.SpatialImplementation.CurrentImplementation.CreateWellKnownTextSqlFormatter(False).Read(Of {0})(New Global.System.IO.StringReader(\"{1}\"))"; } } internal override string Int32TypeName { get { return "Integer"; } } + internal override string ObjectTypeName { get { return "Object"; } } internal override string StringTypeName { get { return "String"; } } internal override string BinaryTypeName { get { return "Byte()"; } } internal override string DecimalTypeName { get { return "Decimal"; } } @@ -4358,6 +4453,7 @@ public sealed class ODataClientVBTemplate : ODataClientTemplate internal override string TimeOfDayTypeName { get { return "Global.Microsoft.OData.Edm.TimeOfDay"; } } internal override string XmlConvertClassName { get { return "Global.System.Xml.XmlConvert"; } } internal override string EnumTypeName { get { return "Global.System.Enum"; } } + internal override string DictionaryTypeName { get { return "Global.System.Collections.Generic.Dictionary(Of {0}, {1})"; } } internal override string FixPattern { get { return "[{0}]"; } } internal override string EnumUnderlyingTypeMarker { get { return " As "; } } internal override string ConstantExpressionConstructorWithType { get { return "Global.System.Linq.Expressions.Expression.Constant({0}, GetType({1}))"; } } @@ -4365,6 +4461,7 @@ public sealed class ODataClientVBTemplate : ODataClientTemplate internal override string UriOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.UriOperationParameter(\"{0}\", {1})"; } } internal override string UriEntityOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.UriEntityOperationParameter(\"{0}\", {1}, {2})"; } } internal override string BodyOperationParameterConstructor { get { return "New Global.Microsoft.OData.Client.BodyOperationParameter(\"{0}\", {1})"; } } + internal override string DictionaryConstructor { get { return $"New {DictionaryTypeName}"; } } internal override string BaseEntityType { get { return "\r\n Inherits Global.Microsoft.OData.Client.BaseEntityType"; } } internal override string OverloadsModifier { get { return "Overloads "; } } internal override string ODataVersion { get { return "Global.Microsoft.OData.ODataVersion.V4"; } } diff --git a/src/Unchase.OData.ConnectedService/ViewModels/AdvancedSettingsViewModel.cs b/src/Unchase.OData.ConnectedService/ViewModels/AdvancedSettingsViewModel.cs index cf26591..ceb150e 100644 --- a/src/Unchase.OData.ConnectedService/ViewModels/AdvancedSettingsViewModel.cs +++ b/src/Unchase.OData.ConnectedService/ViewModels/AdvancedSettingsViewModel.cs @@ -88,6 +88,34 @@ public bool IgnoreUnexpectedElementsAndAttributes } #endregion + #region GenerateDynamicPropertiesCollection + private bool _generateDynamicPropertiesCollection; + public bool GenerateDynamicPropertiesCollection + { + get => _generateDynamicPropertiesCollection; + set + { + _generateDynamicPropertiesCollection = value; + UserSettings.GenerateDynamicPropertiesCollection = value; + OnPropertyChanged(nameof(GenerateDynamicPropertiesCollection)); + } + } + #endregion + + #region DynamicPropertiesCollectionName + private string _dynamicPropertiesCollectionName; + public string DynamicPropertiesCollectionName + { + get => _dynamicPropertiesCollectionName; + set + { + _dynamicPropertiesCollectionName = value; + UserSettings.DynamicPropertiesCollectionName = value; + OnPropertyChanged(nameof(DynamicPropertiesCollectionName)); + } + } + #endregion + #region GeneratedFileNameEnabled public bool GeneratedFileNameEnabled { get; set; } #endregion @@ -211,6 +239,8 @@ public override async Task OnPageEnteringAsync(WizardEnteringArgs args) this.NamespacePrefix = UserSettings.NamespacePrefix ?? Constants.DefaultNamespacePrefix; this.EnableNamingAlias = UserSettings.EnableNamingAlias; this.IgnoreUnexpectedElementsAndAttributes = UserSettings.IgnoreUnexpectedElementsAndAttributes; + this.GenerateDynamicPropertiesCollection = UserSettings.GenerateDynamicPropertiesCollection; + this.DynamicPropertiesCollectionName = UserSettings.DynamicPropertiesCollectionName; this.GeneratedFileNameEnabled = true; this.GeneratedFileNamePrefix = UserSettings.GeneratedFileNamePrefix ?? Constants.DefaultReferenceFileName; this.IncludeT4FileEnabled = true; diff --git a/src/Unchase.OData.ConnectedService/Views/AdvancedSettings.xaml b/src/Unchase.OData.ConnectedService/Views/AdvancedSettings.xaml index 43d9766..a589798 100644 --- a/src/Unchase.OData.ConnectedService/Views/AdvancedSettings.xaml +++ b/src/Unchase.OData.ConnectedService/Views/AdvancedSettings.xaml @@ -147,6 +147,24 @@ IsChecked="{Binding UserSettings.IgnoreUnexpectedElementsAndAttributes, Mode=TwoWay}"> Ignore unknown elements (Whether to ignore unexpected elements and attributes in the metadata document and generate the client code if any). + + + Generate Dynamic Properties Collection with name : + + +