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

V3: Option to have lazy DSC<> initialization. Option to exclude static model #46

Closed
Closed
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
184 changes: 184 additions & 0 deletions ODataConnectedService/src/CodeGeneration/V3CodeGenDescriptor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,117 @@ namespace Microsoft.OData.ConnectedService.CodeGeneration
{
internal class V3CodeGenDescriptor : BaseCodeGenDescriptor
{
const string loadServiceModelAssignment = "this.Format.LoadServiceModel = GeneratedEdmModel.GetInstance;";
const string onContextCreatedCall = "this.OnContextCreated();";
const string dscPattern = @"private (.+) _(.+) = new global::System.Data.Services.Client.DataServiceCollection\<(.+)\>\(null, global::System.Data.Services.Client.TrackingMode.None\);";
const string collectionPattern = @"private (.+) _(.+) = new global::System.Collections.ObjectModel.Collection\<(.+)\>\(\);";

const string dynamicModelLoader =
@"/// <summary>
/// Runtime IEdmModel loader class. Responsible for loading, parsing and caching $metadata documents.
/// Used in place of the static GeneratedEdmModel from this.Format.LoadServiceModel().
/// </summary>
private static class RuntimeEdmModel
{
[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Data.Services.Design"", ""1.0.0"")]
private static global::System.Collections.Generic.Dictionary<string, global::Microsoft.Data.Edm.IEdmModel> models =
new global::System.Collections.Generic.Dictionary<string, global::Microsoft.Data.Edm.IEdmModel>(global::System.StringComparer.OrdinalIgnoreCase);

[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Data.Services.Design"", ""1.0.0"")]
private static readonly object modelsCacheLock = new object();

[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Data.Services.Design"", ""1.0.0"")]
public static global::Microsoft.Data.Edm.IEdmModel LoadModel(global::System.Data.Services.Client.DataServiceContext context)
{
var metadataUri = context.GetMetadataUri();
global::Microsoft.Data.Edm.IEdmModel model = null;
if (!models.TryGetValue(metadataUri.AbsoluteUri, out model))
{
lock (modelsCacheLock)
{
if (!models.TryGetValue(metadataUri.AbsoluteUri, out model))
{
var request = (global::System.Net.HttpWebRequest)global::System.Net.WebRequest.Create(metadataUri);
request.Credentials = context.Credentials;

using (var response = request.EndGetResponse(request.BeginGetResponse(null, null)))
using (var stream = response.GetResponseStream())
using (var reader = global::System.Xml.XmlReader.Create(stream))
{
model = global::Microsoft.Data.Edm.Csdl.EdmxReader.Parse(reader);
models.Add(metadataUri.AbsoluteUri, model);
}
}
}
}
return model;
}
}";

const string resolveBackingFieldAssignment = "this.ResolveBackingField = ResolveCodeGenBackingField;\r\n\t\t\t";
const string resolveBackingFieldImplementation = @"
private static global::System.Reflection.FieldInfo FindBackingField(global::System.Reflection.PropertyInfo property, string prefix)
{
global::System.Reflection.FieldInfo backingField = null;
#if NO_NETFX
// global::System.Reflection.BindingFlags is only available on full .Net Framework.
// Developers can add NO_NETFX to Conditional compilation symbols and still use lazy collection initialization.
backingField = global::System.Linq.Queryable.FirstOrDefault(
global::System.Linq.Queryable.Where(
global::System.Linq.Queryable.AsQueryable(global::System.Reflection.RuntimeReflectionExtensions.GetRuntimeFields(property.DeclaringType)),
p => p.Name == prefix + property.Name && !p.IsStatic && !p.IsPublic && p.FieldType == property.PropertyType));
#else
backingField = property.DeclaringType.GetField(prefix + property.Name, global::System.Reflection.BindingFlags.NonPublic | global::System.Reflection.BindingFlags.Instance);
if (backingField != null && backingField.FieldType != property.PropertyType)
{
backingField = null;
}
#endif
return backingField;
}
/// <summary>
/// Tries to find the backing field for property.
/// This is needed when using lazy initialized collection properties to avoid WCF DataServiesClient triggering construction.
/// The backing field name is matched by name using the naming convention by the code generation process.
/// </summary>
/// <param name=""property"">The property to find the backing field for.</param>
/// <returns>The property backing FieldInfo or null.</returns>
private static global::System.Reflection.FieldInfo ResolveCodeGenBackingField(global::System.Reflection.PropertyInfo property)
{
if (property == null)
return null;

global::System.Reflection.FieldInfo backingField = FindBackingField(property, ""__"");
if (backingField == null)
backingField = FindBackingField(property, ""_"");

return backingField;
}
" + "\t\t";

const string collectionReplacer =
@"private $1 __$2 = null;

[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Data.Services.Design"", ""1.0.0"")]
private $1 _$2
{
get
{
if (__$2 == null && !__$2Initialized)
$2 = new $1(null, global::System.Data.Services.Client.TrackingMode.None);
return __$2;
}
set
{
__$2 = value;
__$2Initialized = true;
}
}

[global::System.CodeDom.Compiler.GeneratedCodeAttribute(""System.Data.Services.Design"", ""1.0.0"")]
bool __$2Initialized;
";

public V3CodeGenDescriptor(string metadataUri, ConnectedServiceHandlerContext context, Project project)
: base(metadataUri, context, project)
{
Expand Down Expand Up @@ -85,6 +196,79 @@ public async override Task AddGeneratedClientCode()
}
}

if (this.ServiceConfiguration.LazyInitializedEntityCollections || this.ServiceConfiguration.UseRuntimeModel)
{
await this.Context.Logger.WriteMessageAsync(LoggerMessageCategory.Information, "Cleaning proxy...");
string original = null;
using (var textReader = File.OpenText(tempFile))
{
original = await textReader.ReadToEndAsync();
}
string modified = null;

if (this.ServiceConfiguration.LazyInitializedEntityCollections)
{
string pattern = null;
if (generator.UseDataServiceCollection)
{
await this.Context.Logger.WriteMessageAsync(LoggerMessageCategory.Information, "Proxy - injecting lazy initialization of DataServiceCollections");
pattern = dscPattern;
}
else
{
await this.Context.Logger.WriteMessageAsync(LoggerMessageCategory.Information, "Proxy - injecting lazy initialization of ObservableCollections");
pattern = collectionPattern;
}
modified = Regex.Replace(original, pattern, collectionReplacer);
int classStart = modified.IndexOf("private abstract class GeneratedEdmModel");
if (classStart >= 0)
{
modified = string.Concat(
modified.Substring(0, classStart),
resolveBackingFieldImplementation,
modified.Substring(classStart));
int onContextCreatedCallIndex = modified.IndexOf(onContextCreatedCall);
if (onContextCreatedCallIndex >= 0)
{
modified = string.Concat(
modified.Substring(0, onContextCreatedCallIndex),
resolveBackingFieldAssignment,
modified.Substring(onContextCreatedCallIndex));
}
}
}
else
{
modified = original;
}

if (this.ServiceConfiguration.UseRuntimeModel)
{
await this.Context.Logger.WriteMessageAsync(LoggerMessageCategory.Information, "Proxy - removing static model");
int loadServiceModelAssignmentIndex = modified.IndexOf(loadServiceModelAssignment);
if (loadServiceModelAssignmentIndex >= 0)
{
modified = string.Concat(
modified.Substring(0, loadServiceModelAssignmentIndex), "this.Format.LoadServiceModel = () => RuntimeEdmModel.LoadModel(this);",
modified.Substring(loadServiceModelAssignmentIndex + loadServiceModelAssignment.Length));
int classStart = modified.IndexOf("private abstract class GeneratedEdmModel");
int classEnd = modified.IndexOf("return global::System.Xml.XmlReader.Create(new global::System.IO.StringReader(edmxToParse));");
if (classStart >= 0 && classEnd > 0)
{
classEnd = modified.IndexOf('}', classEnd + 1);
classEnd = modified.IndexOf('}', classEnd + 1) + 1;
modified = string.Concat(modified.Substring(0, classStart), dynamicModelLoader, modified.Substring(classEnd));
}
}
}
await this.Context.Logger.WriteMessageAsync(LoggerMessageCategory.Information, "Proxy generation done. Writing output files...");
using (StreamWriter writer = File.CreateText(tempFile))
{
await writer.WriteAsync(modified);
await writer.FlushAsync();
}
}

string outputFile = Path.Combine(GetReferenceFileFolder(), this.GeneratedFileNamePrefix + ".cs");
await this.Context.HandlerHelper.AddFileAsync(tempFile, outputFile);
}
Expand Down
2 changes: 2 additions & 0 deletions ODataConnectedService/src/Models/ServiceConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ internal class ServiceConfiguration
public bool UseNameSpacePrefix { get; set; }
public string NamespacePrefix { get; set; }
public bool UseDataServiceCollection { get; set; }
public bool UseRuntimeModel { get; set; }
public bool LazyInitializedEntityCollections { get; set; }
}

internal class ServiceConfigurationV4 : ServiceConfiguration
Expand Down
4 changes: 4 additions & 0 deletions ODataConnectedService/src/ODataConnectedServiceWizard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ public ODataConnectedServiceWizard(ConnectedServiceProviderContext context)
advancedSettingsViewModel.UseNamespacePrefix = serviceConfig.UseNameSpacePrefix;
advancedSettingsViewModel.NamespacePrefix = serviceConfig.NamespacePrefix;
advancedSettingsViewModel.UseDataServiceCollection = serviceConfig.UseDataServiceCollection;
advancedSettingsViewModel.UseRuntimeModel = serviceConfig.UseRuntimeModel;
advancedSettingsViewModel.LazyInitializedEntityCollections = serviceConfig.LazyInitializedEntityCollections;

if (serviceConfig.EdmxVersion == Common.Constants.EdmxVersion4)
{
Expand Down Expand Up @@ -130,6 +132,8 @@ private ServiceConfiguration CreateServiceConfiguration()
serviceConfiguration.Endpoint = ConfigODataEndpointViewModel.Endpoint;
serviceConfiguration.EdmxVersion = ConfigODataEndpointViewModel.EdmxVersion;
serviceConfiguration.UseDataServiceCollection = AdvancedSettingsViewModel.UseDataServiceCollection;
serviceConfiguration.UseRuntimeModel = AdvancedSettingsViewModel.UseRuntimeModel;
serviceConfiguration.LazyInitializedEntityCollections = AdvancedSettingsViewModel.LazyInitializedEntityCollections;
serviceConfiguration.GeneratedFileNamePrefix = AdvancedSettingsViewModel.GeneratedFileName;
serviceConfiguration.UseNameSpacePrefix = AdvancedSettingsViewModel.UseNamespacePrefix;
if (AdvancedSettingsViewModel.UseNamespacePrefix && !string.IsNullOrEmpty(AdvancedSettingsViewModel.NamespacePrefix))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ internal class AdvancedSettingsViewModel : ConnectedServiceWizardPage
public bool IgnoreUnexpectedElementsAndAttributes { get; set; }
public string GeneratedFileName { get; set; }
public bool IncludeT4File { get; set; }
public bool UseRuntimeModel { get; set; }
public bool LazyInitializedEntityCollections { get; set; }

public AdvancedSettingsViewModel() : base()
{
Expand Down Expand Up @@ -51,6 +53,8 @@ private void ResetDataContext()
this.UseNamespacePrefix = false;
this.NamespacePrefix = null;
this.UseDataServiceCollection = true;
this.UseRuntimeModel = false;
this.LazyInitializedEntityCollections = true;
this.IgnoreUnexpectedElementsAndAttributes = false;
this.EnableNamingAlias = false;
this.GeneratedFileName = Common.Constants.DefaultReferenceFileName;
Expand Down
8 changes: 8 additions & 0 deletions ODataConnectedService/src/Views/AdvancedSettings.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,14 @@
HorizontalAlignment="Left" TextWrapping="Wrap" VerticalAlignment="Center" Width="250" Margin="20, 0, 0, 5"/>
<CheckBox x:Name="UseDSC" Content="Enable entity and property tracking" IsChecked="{Binding UseDataServiceCollection}"
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0, 5, 0, 0" />
<CheckBox x:Name="UseLazyDSC" Content="Lazy entity collection initialization" IsChecked="{Binding LazyInitializedEntityCollections}"
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0, 5, 0, 0" />
<CheckBox x:Name="ExcludeModel" IsChecked="{Binding UseRuntimeModel}"
HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0, 5, 0, 0" >
<TextBlock TextWrapping="Wrap" Margin="0, 0, 40, 0">
Load model runtime (Produces smaller assembly, but will issue a request runtime for the metadata document.)
</TextBlock>
</CheckBox>
</StackPanel>
<StackPanel x:Name="AdvancedSettingsForv4" HorizontalAlignment="Left" Margin="10, 5, 0, 0" VerticalAlignment="Top">
<CheckBox x:Name="EnableCamelCase" Content="Use c# casing style" IsChecked="{Binding EnableNamingAlias}"
Expand Down