Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CodeGen support for emitting dynamic properties on open types #1436

Open
wants to merge 2 commits into
base: release-7.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions src/CodeGen/ODataT4CodeGenerator.tt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<#@ include file="ODataT4CodeGenerator.ttinclude" #>
<#@ include file="ODataT4CodeGenerator.ttinclude" #>
<#+
public static class Configuration
{
Expand Down Expand Up @@ -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<string, object> 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<string, object> 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
Expand Down Expand Up @@ -76,4 +86,4 @@ public static class Customization
return upperNamespace;
}
}
#>
#>
117 changes: 105 additions & 12 deletions src/CodeGen/ODataT4CodeGenerator.ttinclude
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<#
<#
/*
OData Client T4 Template ver. #VersionNumber#
Copyright (c) Microsoft Corporation
Expand Down Expand Up @@ -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
Expand All @@ -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
};
}

Expand Down Expand Up @@ -225,6 +229,24 @@ public bool IgnoreUnexpectedElementsAndAttributes
set;
}

/// <summary>
/// true to generate open type property dirctionary, false otherwise.
/// </summary>
public bool GenerateDynamicPropertiesCollection
{
get;
set;
}

/// <summary>
/// Name of the OpenType dictionary property
/// </summary>
public string DynamicPropertiesCollectionName
{
get;
set;
}

/// <summary>
/// Generate code targeting a specific .Net Framework language.
/// </summary>
Expand Down Expand Up @@ -317,6 +339,26 @@ public void ValidateAndSetIgnoreUnexpectedElementsAndAttributesFromString(string
this.IgnoreUnexpectedElementsAndAttributes = boolValue;
}

/// <summary>
/// Set the GenerateDynamicPropertiesCollection property with the given value.
/// </summary>
/// <param name="stringValue">The value to set.</param>
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;
}

/// <summary>
/// Reads the parameter values from the Configuration class and applies them.
/// </summary>
Expand All @@ -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;
}

/// <summary>
Expand Down Expand Up @@ -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;
}
}

/// <summary>
Expand Down Expand Up @@ -604,8 +660,7 @@ public class CodeGenerationContext
{
Debug.Assert(this.EdmModel != null, "this.EdmModel != null");
this.modelHasInheritance = this.EdmModel.SchemaElementsAcrossModels()
.OfType<IEdmStructuredType>().Any(t => !t.FullTypeName().StartsWith("Org.OData.Authorization.V1") &&
!t.FullTypeName().StartsWith("Org.OData.Capabilities.V1") && t.BaseType != null);
.OfType<IEdmStructuredType>().Any(t => !t.FullTypeName().StartsWith("Org.OData.Authorization.V1") && t.BaseType != null);
}

return this.modelHasInheritance.Value;
Expand Down Expand Up @@ -723,6 +778,24 @@ public class CodeGenerationContext
set;
}

/// <summary>
/// true to generate open type property dirctionary, false otherwise.
/// </summary>
public bool GenerateDynamicPropertiesCollection
{
get;
set;
}

/// <summary>
/// Name of the OpenType dictionary property
/// </summary>
public string DynamicPropertiesCollectionName
{
get;
set;
}

/// <summary>
/// Maps the element type of an entity set to the entity set.
/// </summary>
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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; }
Expand Down Expand Up @@ -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<string> LanguageKeywords { get; }
internal abstract string FixPattern { get; }
internal abstract string EnumUnderlyingTypeMarker { get; }
Expand All @@ -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; }
Expand Down Expand Up @@ -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)
{
Expand All @@ -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)
{
Expand Down Expand Up @@ -2141,7 +2215,7 @@ public abstract class ODataClientTemplate : TemplateBase
}
}

internal void WritePropertiesForStructuredType(IEnumerable<IEdmProperty> properties)
internal void WritePropertiesForStructuredType(IEnumerable<IEdmProperty> properties, bool isOpen)
{
bool useDataServiceCollection = this.context.UseDataServiceCollection;

Expand All @@ -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);
Expand Down Expand Up @@ -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"; } }
Expand Down Expand Up @@ -3105,13 +3193,15 @@ 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}))"; } }
internal override string TypeofFormatter { get { return "typeof({0})"; } }
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"; } }
Expand Down Expand Up @@ -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"; } }
Expand Down Expand Up @@ -4222,13 +4313,15 @@ 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}))"; } }
internal override string TypeofFormatter { get { return "GetType({0})"; } }
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"; } }
Expand Down Expand Up @@ -5184,4 +5277,4 @@ End Namespace
<#+
}
}
#>
#>