diff --git a/src/CodeGen/ODataT4CodeGenerator.tt b/src/CodeGen/ODataT4CodeGenerator.tt index 827915264b..76a0fde213 100644 --- a/src/CodeGen/ODataT4CodeGenerator.tt +++ b/src/CodeGen/ODataT4CodeGenerator.tt @@ -1,4 +1,4 @@ -<#@ include file="ODataT4CodeGenerator.ttinclude" #> +<#@ include file="ODataT4CodeGenerator.ttinclude" #> <#+ public static class Configuration { @@ -28,6 +28,16 @@ public static class Configuration // This flag indicates whether to ignore unexpected elements and attributes in the metadata document and generate // 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"; } public static class Customization @@ -76,4 +86,4 @@ public static class Customization return upperNamespace; } } -#> \ No newline at end of file +#> diff --git a/src/CodeGen/ODataT4CodeGenerator.ttinclude b/src/CodeGen/ODataT4CodeGenerator.ttinclude index 90fe80cd10..95a2a8f24b 100644 --- a/src/CodeGen/ODataT4CodeGenerator.ttinclude +++ b/src/CodeGen/ODataT4CodeGenerator.ttinclude @@ -1,4 +1,4 @@ -<# +<# /* OData Client T4 Template ver. #VersionNumber# Copyright (c) Microsoft Corporation @@ -49,7 +49,9 @@ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI TargetLanguage = this.TargetLanguage, EnableNamingAlias = this.EnableNamingAlias, TempFilePath = this.TempFilePath, - IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes + IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes, + GenerateDynamicPropertiesCollection = this.GenerateDynamicPropertiesCollection, + DynamicPropertiesCollectionName = this.DynamicPropertiesCollectionName }; } else @@ -66,7 +68,9 @@ THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI TargetLanguage = this.TargetLanguage, EnableNamingAlias = this.EnableNamingAlias, TempFilePath = this.TempFilePath, - IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes + IgnoreUnexpectedElementsAndAttributes = this.IgnoreUnexpectedElementsAndAttributes, + GenerateDynamicPropertiesCollection = this.GenerateDynamicPropertiesCollection, + DynamicPropertiesCollectionName = this.DynamicPropertiesCollectionName }; } @@ -225,6 +229,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; + } + /// /// Generate code targeting a specific .Net Framework language. /// @@ -317,6 +339,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. /// @@ -329,6 +371,8 @@ private void ApplyParametersFromConfigurationClass() this.EnableNamingAlias = Configuration.EnableNamingAlias; this.TempFilePath = Configuration.TempFilePath; this.IgnoreUnexpectedElementsAndAttributes = Configuration.IgnoreUnexpectedElementsAndAttributes; + this.GenerateDynamicPropertiesCollection = Configuration.GenerateDynamicPropertiesCollection; + this.DynamicPropertiesCollectionName = Configuration.DynamicPropertiesCollectionName; } /// @@ -376,6 +420,18 @@ 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; + } } /// @@ -604,8 +660,7 @@ public class CodeGenerationContext { Debug.Assert(this.EdmModel != null, "this.EdmModel != null"); this.modelHasInheritance = this.EdmModel.SchemaElementsAcrossModels() - .OfType().Any(t => !t.FullTypeName().StartsWith("Org.OData.Authorization.V1") && - !t.FullTypeName().StartsWith("Org.OData.Capabilities.V1") && t.BaseType != null); + .OfType().Any(t => !t.FullTypeName().StartsWith("Org.OData.Authorization.V1") && t.BaseType != null); } return this.modelHasInheritance.Value; @@ -723,6 +778,24 @@ public class CodeGenerationContext 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; + } + /// /// Maps the element type of an entity set to the entity set. /// @@ -913,9 +986,7 @@ public class CodeGenerationContext tmp.FindDeclaredTerm(CoreVocabularyConstants.OptimisticConcurrency) != null || tmp.FindDeclaredTerm(CapabilitiesVocabularyConstants.ChangeTracking) != null || tmp.FindDeclaredTerm(AlternateKeysVocabularyConstants.AlternateKeys) != null || - tmp.FindDeclaredTerm("Org.OData.Authorization.V1.Authorizations") != null || - tmp.FindDeclaredTerm("Org.OData.Validation.V1.DerivedTypeConstraint") != null || - tmp.FindDeclaredTerm("Org.OData.Community.V1.UrlEscapeFunction") != null) + tmp.FindDeclaredTerm("Org.OData.Authorization.V1.Authorizations") != null) { continue; } @@ -981,6 +1052,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; } @@ -1015,6 +1087,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; } @@ -1023,6 +1096,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; } @@ -1684,7 +1758,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) { @@ -1702,7 +1776,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) { @@ -2141,7 +2215,7 @@ public abstract class ODataClientTemplate : TemplateBase } } - internal void WritePropertiesForStructuredType(IEnumerable properties) + internal void WritePropertiesForStructuredType(IEnumerable properties, bool isOpen) { bool useDataServiceCollection = this.context.UseDataServiceCollection; @@ -2161,6 +2235,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); @@ -3071,6 +3158,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"; } } @@ -3105,6 +3193,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}))"; } } @@ -3112,6 +3201,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"; } } @@ -4188,6 +4278,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"; } } @@ -4222,6 +4313,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}))"; } } @@ -4229,6 +4321,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"; } } @@ -5184,4 +5277,4 @@ End Namespace <#+ } } -#> +#> \ No newline at end of file