/// <summary> /// Default constructor accepting the current <see cref="ClientCodeGenerationOptions"/> context. /// </summary> /// <param name="options">The current <see cref="ClientCodeGenerationOptions"/> options.</param> public ClientProxyFixupCodeDomVisitor(ClientCodeGenerationOptions options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } this._options = options; this._isCSharp = (this._options.Language == "C#"); }
/// <summary> /// Generates client proxy source code using the specified <paramref name="codeGeneratorName"/> in the context /// of the specified <paramref name="host"/>. /// </summary> /// <param name="host">The host for code generation.</param> /// <param name="options">The options to use for code generation.</param> /// <param name="assembliesToLoad">The set of server assemblies to use for analysis and composition.</param> /// <param name="codeGeneratorName">Optional generator name. A <c>null</c> or empty value will select the default generator.</param> /// <returns>The generated source code or <c>null</c> if none was generated.</returns> internal string GenerateCode(ICodeGenerationHost host, ClientCodeGenerationOptions options, IEnumerable <string> assembliesToLoad, string codeGeneratorName) { Debug.Assert(host != null, "host cannot be null"); Debug.Assert(options != null, "options cannot be null"); Debug.Assert(assembliesToLoad != null, "assembliesToLoad cannot be null"); ILogger logger = host as ILogger; DomainServiceCatalog catalog = new DomainServiceCatalog(assembliesToLoad, logger); return(this.GenerateCode(host, options, catalog, assembliesToLoad, codeGeneratorName)); }
/// <summary> /// Creates an <see cref="AppDomain"/> configured for Silverlight code generation. /// </summary> /// <param name="options">The code generation options.</param> internal static void ConfigureAppDomain(ClientCodeGenerationOptions options) { FrameworkManifest frameworkManifest; if (options.ClientProjectTargetPlatform == TargetPlatform.Silverlight) frameworkManifest = GetSilverlightFrameworkManifest(options.ClientFrameworkPath); else frameworkManifest = GetFrameworkManifest(options.ClientFrameworkPath); AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += AppDomainUtilities.ResolveFrameworkAssemblyVersioning; AppDomain.CurrentDomain.SetData(FrameworkManifestKey, frameworkManifest); }
/// <summary> /// Generates client proxy source code using the specified <paramref name="codeGeneratorName"/> in the context /// of the specified <paramref name="host"/>. /// </summary> /// <param name="host">The host for code generation.</param> /// <param name="options">The options to use for code generation.</param> /// <param name="domainServiceTypes">The set of <see cref="OpenRiaServices.DomainServices.Server.DomainService"/> types for which to generate code.</param> /// <param name="compositionAssemblies">The optional set of assemblies to use to create the MEF composition container.</param> /// <param name="codeGeneratorName">Optional generator name. A <c>null</c> or empty value will select the default generator.</param> /// <returns>The generated source code or <c>null</c> if none was generated.</returns> internal string GenerateCode(ICodeGenerationHost host, ClientCodeGenerationOptions options, IEnumerable <Type> domainServiceTypes, IEnumerable <string> compositionAssemblies, string codeGeneratorName) { Debug.Assert(host != null, "host cannot be null"); Debug.Assert(options != null, "options cannot be null"); Debug.Assert(domainServiceTypes != null, "domainServiceTypes cannot be null"); ILogger logger = host as ILogger; DomainServiceCatalog catalog = new DomainServiceCatalog(domainServiceTypes, logger); return(this.GenerateCode(host, options, catalog, compositionAssemblies, codeGeneratorName)); }
/// <summary> /// Validates whether the given <see cref="ClientCodeGenerationOptions"/> options are correct. /// </summary> /// <param name="clientProxyCodeGenerationOptions">Options to validate</param> private static void ValidateOptions(ClientCodeGenerationOptions clientProxyCodeGenerationOptions) { // A null is not acceptable if (clientProxyCodeGenerationOptions == null) { throw new ArgumentNullException(nameof(clientProxyCodeGenerationOptions)); } // The language property may not be null. if (String.IsNullOrEmpty(clientProxyCodeGenerationOptions.Language)) { throw new ArgumentException(Resource.Null_Language_Property, nameof(clientProxyCodeGenerationOptions)); } }
public string GenerateCode(ICodeGenerationHost host, IEnumerable<DomainServiceDescription> descriptions, ClientCodeGenerationOptions options) { try { // Initialize all instance state this.Initialize(host, descriptions, options); // Generate the code return this.GenerateProxyClass(); } finally { // Dispose and release all instance state this.Cleanup(); } }
/// <summary> /// Creates an <see cref="AppDomain"/> configured for Silverlight code generation. /// </summary> /// <param name="options">The code generation options.</param> internal static void ConfigureAppDomain(ClientCodeGenerationOptions options) { FrameworkManifest frameworkManifest; if (options.ClientProjectTargetPlatform == TargetPlatform.Silverlight) { frameworkManifest = GetSilverlightFrameworkManifest(options.ClientFrameworkPath); } else { frameworkManifest = GetFrameworkManifest(options.ClientFrameworkPath); } AppDomain.CurrentDomain.ReflectionOnlyAssemblyResolve += AppDomainUtilities.ResolveFrameworkAssemblyVersioning; AppDomain.CurrentDomain.SetData(FrameworkManifestKey, frameworkManifest); }
internal void Initialize(ICodeGenerationHost host, IEnumerable <DomainServiceDescription> descriptions, ClientCodeGenerationOptions options) { if (host == null) { throw new ArgumentNullException(nameof(host)); } if (options == null) { throw new ArgumentNullException(nameof(options)); } if (descriptions == null) { throw new ArgumentNullException(nameof(descriptions)); } // Initialize all the instance variables this._host = host; this._clientProxyCodeGenerationOptions = options; this._domainServiceDescriptions = descriptions.ToList(); this._compileUnit = new CodeCompileUnit(); this._namespaces = new Dictionary <string, CodeNamespace>(); this._enumTypesToGenerate = new HashSet <Type>(); CodeDomClientCodeGenerator.ValidateOptions(this._clientProxyCodeGenerationOptions); // Unconditionally initialize some options CodeGeneratorOptions cgo = new CodeGeneratorOptions(); cgo.IndentString = " "; cgo.VerbatimOrder = false; cgo.BlankLinesBetweenMembers = true; cgo.BracingStyle = "C"; this._options = cgo; // Choose the provider for the language. C# is the default if unspecified. string language = this.ClientProxyCodeGenerationOptions.Language; bool isCSharp = String.IsNullOrEmpty(language) || String.Equals(language, "C#", StringComparison.OrdinalIgnoreCase); this._provider = isCSharp ? (CodeDomProvider) new CSharpCodeProvider() : (CodeDomProvider) new VBCodeProvider(); // Configure our code gen utility package CodeGenUtilities.Initialize(!this.IsCSharp, this.ClientProxyCodeGenerationOptions.UseFullTypeNames, this.ClientProxyCodeGenerationOptions.ClientRootNamespace); }
internal void Initialize(ICodeGenerationHost host, IEnumerable<DomainServiceDescription> descriptions, ClientCodeGenerationOptions options) { if (host == null) { throw new ArgumentNullException("host"); } if (options == null) { throw new ArgumentNullException("options"); } if (descriptions == null) { throw new ArgumentNullException("descriptions"); } // Initialize all the instance variables this._host = host; this._clientProxyCodeGenerationOptions = options; this._domainServiceDescriptions = descriptions.ToList(); this._compileUnit = new CodeCompileUnit(); this._namespaces = new Dictionary<string, CodeNamespace>(); this._enumTypesToGenerate = new HashSet<Type>(); CodeDomClientCodeGenerator.ValidateOptions(this._clientProxyCodeGenerationOptions); // Unconditionally initialize some options CodeGeneratorOptions cgo = new CodeGeneratorOptions(); cgo.IndentString = " "; cgo.VerbatimOrder = false; cgo.BlankLinesBetweenMembers = true; cgo.BracingStyle = "C"; this._options = cgo; // Choose the provider for the language. C# is the default if unspecified. string language = this.ClientProxyCodeGenerationOptions.Language; bool isCSharp = String.IsNullOrEmpty(language) || String.Equals(language, "C#", StringComparison.OrdinalIgnoreCase); this._provider = isCSharp ? (CodeDomProvider)new CSharpCodeProvider() : (CodeDomProvider)new VBCodeProvider(); // Configure our code gen utility package CodeGenUtilities.Initialize(!this.IsCSharp, this.ClientProxyCodeGenerationOptions.UseFullTypeNames, this.ClientProxyCodeGenerationOptions.ClientRootNamespace); }
private void Cleanup() { // Dispose and release all instance variables CodeDomProvider provider = this._provider; this._provider = null; if (provider != null) { provider.Dispose(); } this._compileUnit = null; this._namespaces = null; this._enumTypesToGenerate = null; this._domainServiceDescriptions = null; this._host = null; this._options = null; this._clientProxyCodeGenerationOptions = null; }
/// <summary> /// Generates client proxy source code using the specified <paramref name="codeGeneratorName"/> in the context /// of the specified <paramref name="host"/>. /// </summary> /// <param name="host">The host for code generation.</param> /// <param name="options">The options to use for code generation.</param> /// <param name="catalog">The catalog containing the <see cref="OpenRiaServices.DomainServices.Server.DomainService"/> types.</param> /// <param name="compositionAssemblies">The optional set of assemblies to use to create the MEF composition container.</param> /// <param name="codeGeneratorName">Optional generator name. A <c>null</c> or empty value will select the default generator.</param> /// <returns>The generated source code or <c>null</c> if none was generated.</returns> private string GenerateCode(ICodeGenerationHost host, ClientCodeGenerationOptions options, DomainServiceCatalog catalog, IEnumerable <string> compositionAssemblies, string codeGeneratorName) { Debug.Assert(host != null, "host cannot be null"); Debug.Assert(options != null, "options cannot be null"); Debug.Assert(catalog != null, "catalog cannot be null"); IEnumerable <DomainServiceDescription> domainServiceDescriptions = catalog.DomainServiceDescriptions; IDomainServiceClientCodeGenerator proxyGenerator = this.FindCodeGenerator(host, options, compositionAssemblies, codeGeneratorName); string generatedCode = null; if (proxyGenerator != null) { try { generatedCode = proxyGenerator.GenerateCode(host, domainServiceDescriptions, options); } catch (Exception ex) { // Fatal exceptions are never swallowed or processed if (ex.IsFatal()) { throw; } // Any exception from the code generator is caught and reported, otherwise it will // hit the MSBuild backstop and report failure of the custom build task. // It is acceptable to report this exception and "ignore" it because we // are running in a separate AppDomain which will be torn down immediately // after our return. host.LogError(string.Format(CultureInfo.CurrentCulture, Resource.CodeGenerator_Threw_Exception, string.IsNullOrEmpty(codeGeneratorName) ? proxyGenerator.GetType().FullName : codeGeneratorName, options.ClientProjectPath, ex.Message)); } } return(generatedCode); }
/// <summary> /// Generates client proxy source code using the generator specified by <paramref name="codeGeneratorName"/>. /// </summary> /// <param name="options">The options to use for code generation.</param> /// <param name="parameters">The parameters required to create the <see cref="ISharedCodeService"/>.</param> /// <param name="loggingService">The service to use for logging.</param> /// <param name="codeGeneratorName">Optional generator name. A <c>null</c> or empty value will select the default generator.</param> /// <returns>The generated source code or <c>null</c> if none was generated.</returns> internal string GenerateCode(ClientCodeGenerationOptions options, SharedCodeServiceParameters parameters, ILoggingService loggingService, string codeGeneratorName) { Debug.Assert(options != null, "options cannot be null"); Debug.Assert(parameters != null, "parameters cannot be null"); Debug.Assert(loggingService != null, "loggingService cannot be null"); try { AppDomainUtilities.ConfigureAppDomain(options); LoadOpenRiaServicesServerAssembly(parameters, loggingService); using (SharedCodeService sharedCodeService = new SharedCodeService(parameters, loggingService)) { CodeGenerationHost host = new CodeGenerationHost(loggingService, sharedCodeService); return this.GenerateCode(host, options, parameters.ServerAssemblies, codeGeneratorName); } } catch (Exception ex) { // Fatal exceptions are never swallowed or processed if (ex.IsFatal()) { throw; } // Any exception from the code generator is caught and reported, otherwise it will // hit the MSBuild backstop and report failure of the custom build task. // It is acceptable to report this exception and "ignore" it because we // are running in a separate AppDomain which will be torn down immediately // after our return. loggingService.LogError(string.Format(CultureInfo.CurrentCulture, Resource.ClientCodeGenDispatecher_Threw_Exception_Before_Generate, ex.Message)); loggingService.LogException(ex); return null; } }
/// <summary> /// Generates client proxy source code using the generator specified by <paramref name="codeGeneratorName"/>. /// </summary> /// <param name="options">The options to use for code generation.</param> /// <param name="parameters">The parameters required to create the <see cref="ISharedCodeService"/>.</param> /// <param name="loggingService">The service to use for logging.</param> /// <param name="codeGeneratorName">Optional generator name. A <c>null</c> or empty value will select the default generator.</param> /// <returns>The generated source code or <c>null</c> if none was generated.</returns> internal string GenerateCode(ClientCodeGenerationOptions options, SharedCodeServiceParameters parameters, ILoggingService loggingService, string codeGeneratorName) { Debug.Assert(options != null, "options cannot be null"); Debug.Assert(parameters != null, "parameters cannot be null"); Debug.Assert(loggingService != null, "loggingService cannot be null"); try { AppDomainUtilities.ConfigureAppDomain(options); LoadOpenRiaServicesServerAssembly(parameters, loggingService); using (SharedCodeService sharedCodeService = new SharedCodeService(parameters, loggingService)) { CodeGenerationHost host = new CodeGenerationHost(loggingService, sharedCodeService); return(this.GenerateCode(host, options, parameters.ServerAssemblies, codeGeneratorName)); } } catch (Exception ex) { // Fatal exceptions are never swallowed or processed if (ex.IsFatal()) { throw; } // Any exception from the code generator is caught and reported, otherwise it will // hit the MSBuild backstop and report failure of the custom build task. // It is acceptable to report this exception and "ignore" it because we // are running in a separate AppDomain which will be torn down immediately // after our return. loggingService.LogError(string.Format(CultureInfo.CurrentCulture, Resource.ClientCodeGenDispatecher_Threw_Exception_Before_Generate, ex.Message)); loggingService.LogException(ex); return(null); } }
/// <summary> /// Generates client proxy source code using the specified <paramref name="codeGeneratorName"/> in the context /// of the specified <paramref name="host"/>. /// </summary> /// <param name="host">The host for code generation.</param> /// <param name="options">The options to use for code generation.</param> /// <param name="catalog">The catalog containing the <see cref="OpenRiaServices.DomainServices.Server.DomainService"/> types.</param> /// <param name="compositionAssemblies">The optional set of assemblies to use to create the MEF composition container.</param> /// <param name="codeGeneratorName">Optional generator name. A <c>null</c> or empty value will select the default generator.</param> /// <returns>The generated source code or <c>null</c> if none was generated.</returns> private string GenerateCode(ICodeGenerationHost host, ClientCodeGenerationOptions options, DomainServiceCatalog catalog, IEnumerable<string> compositionAssemblies, string codeGeneratorName) { Debug.Assert(host != null, "host cannot be null"); Debug.Assert(options != null, "options cannot be null"); Debug.Assert(catalog != null, "catalog cannot be null"); IEnumerable<DomainServiceDescription> domainServiceDescriptions = catalog.DomainServiceDescriptions; IDomainServiceClientCodeGenerator proxyGenerator = this.FindCodeGenerator(host, options, compositionAssemblies, codeGeneratorName); string generatedCode = null; if (proxyGenerator != null) { try { generatedCode = proxyGenerator.GenerateCode(host, domainServiceDescriptions, options); } catch (Exception ex) { // Fatal exceptions are never swallowed or processed if (ex.IsFatal()) { throw; } // Any exception from the code generator is caught and reported, otherwise it will // hit the MSBuild backstop and report failure of the custom build task. // It is acceptable to report this exception and "ignore" it because we // are running in a separate AppDomain which will be torn down immediately // after our return. host.LogError(string.Format(CultureInfo.CurrentCulture, Resource.CodeGenerator_Threw_Exception, string.IsNullOrEmpty(codeGeneratorName) ? proxyGenerator.GetType().FullName : codeGeneratorName, options.ClientProjectPath, ex.Message)); } } return generatedCode; }
// Locates the code generator registered to work with the language for the given options internal static IDomainServiceClientCodeGenerator CreateCodeGenerator(ICodeGenerationHost host, ClientCodeGenerationOptions options) { using (ClientCodeGenerationDispatcher dispatcher = new ClientCodeGenerationDispatcher()) { IDomainServiceClientCodeGenerator generator = dispatcher.FindCodeGenerator(host, options, /*compositionAssemblies*/ null, /*codeGeneratorName*/ null); return generator; } }
/// <summary> /// Generates the client proxies. /// </summary> /// <remarks> /// This method validates the presence of the necessary input server assemblies and /// then invokes the code generation logic to create a file containing the client /// proxies for the discovered server's Business Objects. If the file already exists /// and is newer than the inputs, this method does nothing. /// <para>In all success paths, the client proxy file will be added to the list of generated /// files so the custom targets file can add them to the @Compile collection.</para> /// </remarks> internal void GenerateClientProxies() { IEnumerable<string> assemblies = this.GetServerAssemblies(); IEnumerable<string> references = this.GetReferenceAssemblies(); // We will load all input and reference assemblies IEnumerable<string> assembliesToLoad = assemblies.Concat(references); // It is a failure if any of the reference assemblies are missing if (!this.EnsureAssembliesExist(references)) { return; } // Obtain the name of the output assembly from the server project // (it is currently a collection to be consistent with MSBuild item collections). // If there is no output assembly, log a warning. // We consider this non-fatal because an Intellisense build can trivially // encounter this immediately after creating a new Open Ria Services application string assemblyFile = assemblies.FirstOrDefault(); if (string.IsNullOrEmpty(assemblyFile) || !File.Exists(assemblyFile)) { string serverProjectFile = Path.GetFileName(this.ServerProjectPath); this.LogWarning(string.Format(CultureInfo.CurrentCulture, Resource.ClientCodeGen_No_Input_Assemblies, serverProjectFile)); return; } // Make it an absolute path and append the language-specific extension string generatedFileName = Path.Combine(this.GeneratedCodePath, this.GenerateProxyFileName(assemblyFile)); // We maintain cached lists of references we used in prior builds. // Determine whether our current inputs are different from the last build that generated code. bool serverReferencesChanged = this.HaveReferencesChanged(this.ServerReferenceListPath(), assembliesToLoad, this.ServerProjectDirectory); bool clientReferencesChanged = this.HaveReferencesChanged(this.ClientReferenceListPath(), this.ClientReferenceAssembliesNormalized, this.ClientProjectDirectory); // Any change in the assembly references for either client or server are grounds to re-gen code. // Developer note -- we use the fact that the inputs have changed to trigger the full code-gen pass. bool needToGenerate = serverReferencesChanged || clientReferencesChanged; // Also trigger code-gen if the generated file is absent or empty. // Technically, the reference-change test is enough, but experience has shown users // manually delete the GeneratedCode folder and expect the next build to recreate it. // Therefore, its absence always triggers a code-gen pass, even though this has the // negative perf impact of causing a full code gen pass everytime until errors have been // resolved. if (!needToGenerate) { FileInfo fileInfo = new FileInfo(generatedFileName); bool fileExists = fileInfo.Exists; needToGenerate = (!fileExists || (fileInfo.Length == 0)); // If we determine the generated // file has been touched since we last analyzed our server references, it is an indication // the user modified the generated file. So force a code gen and // force a rewrite of the server reference file to short circuit this same code next build. if (!needToGenerate && fileExists && File.Exists(this.ServerReferenceListPath())) { if (File.GetLastWriteTime(generatedFileName) > File.GetLastWriteTime(this.ServerReferenceListPath())) { needToGenerate = true; serverReferencesChanged = true; } } } // If we need to generate the file, do that now if (needToGenerate) { // Warn the user if the server assembly has no PDB this.WarnIfNoPdb(assemblyFile); string generatedFileContent = string.Empty; // We override the default parameter to ask for ForceDebug, otherwise the PDB is not copied. ClientBuildManagerParameter cbmParameter = new ClientBuildManagerParameter() { PrecompilationFlags = PrecompilationFlags.ForceDebug, }; string sourceDir = this.ServerProjectDirectory; string targetDir = null; using (ClientBuildManager cbm = new ClientBuildManager(/* appVDir */ "/", sourceDir, targetDir, cbmParameter)) { // Capture the list of assemblies to load into an array to marshal across AppDomains string[] assembliesToLoadArray = assembliesToLoad.ToArray(); // Create the list of options we will pass to the generator. // This instance is serializable and can cross AppDomains ClientCodeGenerationOptions options = new ClientCodeGenerationOptions() { Language = this.Language, ClientFrameworkPath = this.ClientFrameworkPath, ClientRootNamespace = this.ClientProjectRootNamespace, ServerRootNamespace = this.ServerProjectRootNameSpace, ClientProjectPath = this.ClientProjectPath, ServerProjectPath = this.ServerProjectPath, IsApplicationContextGenerationEnabled = this.IsClientApplicationAsBool, UseFullTypeNames = this.UseFullTypeNamesAsBool, ClientProjectTargetPlatform = this.ClientTargetPlatform, }; // The other AppDomain gets a logger that will log back to this AppDomain CrossAppDomainLogger logger = new CrossAppDomainLogger((ILoggingService)this); // Compose the parameters we will pass to the other AppDomain to create the SharedCodeService SharedCodeServiceParameters sharedCodeServiceParameters = this.CreateSharedCodeServiceParameters(assembliesToLoadArray); // Surface a HttpRuntime initialization error that would otherwise manifest as a NullReferenceException // This can occur when the build environment is configured incorrectly if (System.Web.Hosting.HostingEnvironment.InitializationException != null) { throw new InvalidOperationException( Resource.HttpRuntimeInitializationError, System.Web.Hosting.HostingEnvironment.InitializationException); } // Create the "dispatcher" in the 2nd AppDomain. // This object will find and invoke the appropriate code generator using (ClientCodeGenerationDispatcher dispatcher = (ClientCodeGenerationDispatcher)cbm.CreateObject(typeof(ClientCodeGenerationDispatcher), false)) { // Transfer control to the dispatcher in the 2nd AppDomain to locate and invoke // the appropriate code generator. generatedFileContent = dispatcher.GenerateCode(options, sharedCodeServiceParameters, logger, this.CodeGeneratorName); } } // Tell the user where we are writing the generated code if (!string.IsNullOrEmpty(generatedFileContent)) { this.LogMessage(string.Format(CultureInfo.CurrentCulture, Resource.Writing_Generated_Code, generatedFileName)); } // If VS is hosting us, write to its TextBuffer, else simply write to disk // If the file is empty, delete it. this.WriteOrDeleteFileToVS(generatedFileName, generatedFileContent, /*forceWriteToFile*/ false); } else { // Log a message telling user we are skipping code gen because the inputs are older than the generated code this.LogMessage(string.Format(CultureInfo.CurrentCulture, Resource.ClientCodeGen_Skipping_CodeGen, generatedFileName)); } // We unconditionally declare the file was generated if it exists // on disk after this method finishes, even if it was not modified. // This permits the targets file to add it to the @COMPILE collection. // This also prevents adding it to the list if it was deleted above if (File.Exists(generatedFileName)) { this.AddGeneratedFile(generatedFileName); } // Write out reference lists if they have changed if (serverReferencesChanged) { this.WriteReferenceList(this.ServerReferenceListPath(), assembliesToLoad, this.ServerProjectDirectory); } if (clientReferencesChanged) { this.WriteReferenceList(this.ClientReferenceListPath(), this.ClientReferenceAssembliesNormalized, this.ClientProjectDirectory); } return; }
/// <summary> /// Locates and returns the <see cref="IDomainServiceClientCodeGenerator"/> to use to generate client proxies /// for the specified <paramref name="options"/>. /// </summary> /// <param name="host">The host for code generation.</param> /// <param name="options">The options to use for code generation.</param> /// <param name="compositionAssemblies">The optional set of assemblies to use to create the MEF composition container.</param> /// <param name="codeGeneratorName">Optional generator name. A <c>null</c> or empty value will select the default generator.</param> /// <returns>The code generator to use, or <c>null</c> if a matching one could not be found.</returns> internal IDomainServiceClientCodeGenerator FindCodeGenerator(ICodeGenerationHost host, ClientCodeGenerationOptions options, IEnumerable<string> compositionAssemblies, string codeGeneratorName) { Debug.Assert(host != null, "host cannot be null"); Debug.Assert(options != null, "options cannot be null"); if (string.IsNullOrEmpty(options.Language)) { throw new ArgumentException(Resource.Null_Language_Property, "options"); } IDomainServiceClientCodeGenerator generator = null; // Try to load the code generator directly if given an assembly qualified name. // We insist on at least one comma in the name to know this is an assembly qualified name. // Otherwise, we might succeed in loading a dotted name that happens to be in our assembly, // such as the default CodeDom generator. if (!string.IsNullOrEmpty(codeGeneratorName) && codeGeneratorName.Contains(',')) { Type codeGeneratorType = Type.GetType(codeGeneratorName, /*throwOnError*/ false); if (codeGeneratorType != null) { if (!typeof(IDomainServiceClientCodeGenerator).IsAssignableFrom(codeGeneratorType)) { // If generator is of the incorrect type, we will still allow the MEF approach below // to find a better one. This path could be exercised by inadvertantly using a name // that happened to load some random type that was not a code generator. host.LogWarning(string.Format(CultureInfo.CurrentCulture, Resource.Code_Generator_Incorrect_Type, codeGeneratorName)); } else { try { generator = Activator.CreateInstance(codeGeneratorType) as IDomainServiceClientCodeGenerator; } catch (Exception e) { // The open catch of Exception is acceptable because we unconditionally report // the error and are running in a separate AppDomain. if (e.IsFatal()) { throw; } host.LogError(string.Format(CultureInfo.CurrentCulture, Resource.Code_Generator_Instantiation_Error, codeGeneratorName, e.Message)); } } } } if (generator == null) { // Create the MEF composition container (once only) from the assemblies we are analyzing this.CreateCompositionContainer(compositionAssemblies, host as ILogger); // The following property is filled by MEF by the line above. if (this.DomainServiceClientCodeGenerators != null && this.DomainServiceClientCodeGenerators.Any()) { // Select only those registered for the required language IEnumerable<Lazy<IDomainServiceClientCodeGenerator, ICodeGeneratorMetadata>> allImportsForLanguage = this.DomainServiceClientCodeGenerators.Where(i => string.Equals(options.Language, i.Metadata.Language, StringComparison.OrdinalIgnoreCase)); Lazy<IDomainServiceClientCodeGenerator, ICodeGeneratorMetadata> lazyImport = null; // If client specified a specific generator, use that one. // If it cannot be found, log an error to explain the problem. // If multiple with that name are found, log an error and explain the problem. // We consider this an error because the user has explicitly named a generator, // meaning they would not expect the default to be used. if (!string.IsNullOrEmpty(codeGeneratorName)) { IEnumerable<Lazy<IDomainServiceClientCodeGenerator, ICodeGeneratorMetadata>> allImportsForLanguageAndName = allImportsForLanguage.Where(i => string.Equals(i.Metadata.GeneratorName, codeGeneratorName, StringComparison.OrdinalIgnoreCase)); int numberOfMatchingGenerators = allImportsForLanguageAndName.Count(); // No generator with that name was found. Log an error and explain how to register one. if (numberOfMatchingGenerators == 0) { host.LogError(string.Format(CultureInfo.CurrentCulture, Resource.Code_Generator_Not_Found, codeGeneratorName, options.Language, options.ServerProjectPath, options.ClientProjectPath, CodeDomClientCodeGenerator.GeneratorName)); } else if (numberOfMatchingGenerators == 1) { // Exactly one was found -- take it lazyImport = allImportsForLanguageAndName.First(); } else { // Multiple with that name were found. Explain how to remove some of them or // explicitly name one. StringBuilder sb = new StringBuilder(); foreach (var import in allImportsForLanguageAndName.OrderBy(i => i.Value.GetType().FullName)) { sb.AppendLine(" " + import.Value.GetType().FullName); } host.LogError(string.Format(CultureInfo.CurrentCulture, Resource.Multiple_Named_Code_Generators, codeGeneratorName, options.Language, sb.ToString(), options.ServerProjectPath, options.ClientProjectPath, allImportsForLanguageAndName.First().Value.GetType().AssemblyQualifiedName)); } } else { // We are here if no generator name was specified. // If only one import matched the language, we have it. // This is the most common path to discovery of our own CodeDom generator // but will work equally well when it replaced. if (allImportsForLanguage.Count() == 1) { lazyImport = allImportsForLanguage.First(); } else { // Multiple custom generators exist, but a specific generator name was not provided. // Look for any custom generators other than our default CodeDom one. // If we find there is only one custom generator registered, we use that one rather than the default IEnumerable<Lazy<IDomainServiceClientCodeGenerator, ICodeGeneratorMetadata>> customGeneratorImports = allImportsForLanguage.Where(i => !string.Equals(CodeDomClientCodeGenerator.GeneratorName, i.Metadata.GeneratorName, StringComparison.OrdinalIgnoreCase)); int generatorCount = customGeneratorImports.Count(); // Exactly 1 custom generator that is not the default -- take it if (generatorCount == 1) { lazyImport = customGeneratorImports.First(); host.LogMessage(string.Format(CultureInfo.CurrentCulture, Resource.Using_Custom_Code_Generator, lazyImport.Metadata.GeneratorName)); } else if (generatorCount != 0) { // Multiple generators are available but we have insufficient information // to choose one. Log an warning and use the default StringBuilder sb = new StringBuilder(); // Sort for unit test predictability IEnumerable<Lazy<IDomainServiceClientCodeGenerator, ICodeGeneratorMetadata>> orderedCustomGenerators = customGeneratorImports.OrderBy(i => i.Metadata.GeneratorName); foreach (var import in orderedCustomGenerators) { sb.AppendLine(" " + import.Metadata.GeneratorName); } host.LogWarning(string.Format(CultureInfo.CurrentCulture, Resource.Multiple_Custom_Code_Generators_Using_Default, options.Language, sb.ToString(), options.ClientProjectPath, orderedCustomGenerators.First().Metadata.GeneratorName, CodeDomClientCodeGenerator.GeneratorName)); // Pick the default. There should be one, but if not, the calling methods will detect and report a problem. lazyImport = allImportsForLanguage.FirstOrDefault(i => string.Equals(CodeDomClientCodeGenerator.GeneratorName, i.Metadata.GeneratorName, StringComparison.OrdinalIgnoreCase)); } } } generator = lazyImport == null ? null : lazyImport.Value; } } return generator; }
/// <summary> /// Validates whether the given <see cref="ClientCodeGenerationOptions"/> options are correct. /// </summary> /// <param name="clientProxyCodeGenerationOptions">Options to validate</param> private static void ValidateOptions(ClientCodeGenerationOptions clientProxyCodeGenerationOptions) { // A null is not acceptable if (clientProxyCodeGenerationOptions == null) { throw new ArgumentNullException("clientProxyCodeGenerationOptions"); } // The language property may not be null. if (String.IsNullOrEmpty(clientProxyCodeGenerationOptions.Language)) { throw new ArgumentException(Resource.Null_Language_Property, "clientProxyCodeGenerationOptions"); } }
private void Cleanup() { // Dispose and release all instance variables CodeDomProvider provider = this._provider; this._provider = null; if (provider != null) { provider.Dispose(); } this._compileUnit = null; this._namespaces = null; this._enumTypesToGenerate = null; this._domainServiceDescriptions = null; this._host = null; this._options = null; this._clientProxyCodeGenerationOptions = null; }
public string GenerateCode(ICodeGenerationHost codeGenerationHost, IEnumerable<DomainServiceDescription> domainServiceDescriptions, ClientCodeGenerationOptions options) { throw new NotImplementedException(); }
public string GenerateCode(ICodeGenerationHost host, IEnumerable <DomainServiceDescription> descriptions, ClientCodeGenerationOptions options) { try { // Initialize all instance state this.Initialize(host, descriptions, options); // Generate the code return(this.GenerateProxyClass()); } finally { // Dispose and release all instance state this.Cleanup(); } }
/// <summary> /// This method is part of the <see cref="IDomainServiceClientCodeGenerator" /> interface. The RIA Services Code Generation process uses this method as the entry point into the code generator. /// </summary> /// <param name="codeGenerationHost">The code generation host for this instance.</param> /// <param name="domainServiceDescriptions">The list of all the DomainServiceDescription objects.</param> /// <param name="options">The code generation objects.</param> /// <returns>The generated code.</returns> public string GenerateCode(ICodeGenerationHost codeGenerationHost, IEnumerable<DomainServiceDescription> domainServiceDescriptions, ClientCodeGenerationOptions options) { this._codeGenerationHost = codeGenerationHost; this._domainServiceDescriptions = domainServiceDescriptions; this._options = options; this._enumTypesToGenerate = new HashSet<Type>(); if (this.EntityGenerator == null) { this.CodeGenerationHost.LogError(string.Format(CultureInfo.CurrentCulture, TextTemplateResource.EntityGeneratorNotFound)); } if (this.ComplexObjectGenerator == null) { this.CodeGenerationHost.LogError(string.Format(CultureInfo.CurrentCulture, TextTemplateResource.ComplexObjectGeneratorNotFound)); } if (this.DomainContextGenerator == null) { this.CodeGenerationHost.LogError(string.Format(CultureInfo.CurrentCulture, TextTemplateResource.DomainContextGeneratorNotFound)); } if (this.WebContextGenerator == null) { this.CodeGenerationHost.LogError(string.Format(CultureInfo.CurrentCulture, TextTemplateResource.WebContextGeneratorNotFound)); } if (this.EnumGenerator == null) { this.CodeGenerationHost.LogError(string.Format(CultureInfo.CurrentCulture, TextTemplateResource.EnumGeneratorNotFound)); } if (!this.CodeGenerationHost.HasLoggedErrors) { return this.GenerateCode(); } return null; }
internal static ClientCodeGenerationOptions CreateMockCodeGenContext(string language, bool useFullTypeNames) { ClientCodeGenerationOptions options = new ClientCodeGenerationOptions() { Language = language, ClientProjectPath = "MockProject.csproj", UseFullTypeNames = useFullTypeNames }; return options; }
/// <summary> /// Validates code gen for a specific language by comparing it against a file containing the expected output. /// </summary> /// <param name="codeGenOptions">The options specifying the type of validation to perform</param> /// <returns>A command that updates the comparison file</returns> internal static string ValidateLanguageCodeGen(CodeGenValidationOptions codeGenOptions) { Assert.IsFalse(string.IsNullOrEmpty(codeGenOptions.Language)); string outDataDir = TestHelper.GetOutputTestDataDir(codeGenOptions.RelativeDeployDir); string extension = TestHelper.ExtensionFromLanguage(codeGenOptions.Language); string diffMessage = string.Empty; // Compose the abs path to where the test file got deployed by MSTest string referenceFileName = TestHelper.GetTestFileName(codeGenOptions.RelativeTestDir, codeGenOptions.BaseReferenceFileName + extension); Assert.IsTrue(File.Exists(referenceFileName), "Cannot find reference file " + referenceFileName); string generatedCode = string.Empty; ClientCodeGenerationOptions options = new ClientCodeGenerationOptions() { Language = codeGenOptions.Language, ClientRootNamespace = codeGenOptions.RootNamespace, ClientProjectPath = "MockProject.proj", IsApplicationContextGenerationEnabled = codeGenOptions.GenerateApplicationContexts, UseFullTypeNames = codeGenOptions.UseFullTypeNames, ClientProjectTargetPlatform = TargetPlatform.Silverlight }; MockCodeGenerationHost host = TestHelper.CreateMockCodeGenerationHost(codeGenOptions.Logger, codeGenOptions.SharedCodeService); ILogger logger = host as ILogger; DomainServiceCatalog catalog = new DomainServiceCatalog(codeGenOptions.DomainServiceTypes, logger); IDomainServiceClientCodeGenerator generator; using (ClientCodeGenerationDispatcher dispatcher = new ClientCodeGenerationDispatcher()) { generator = dispatcher.FindCodeGenerator(host, options, /*compositionAssemblies*/ null, /*codeGeneratorName*/ null); } Assert.IsNotNull(generator, "Failed to find a code generator"); generatedCode = generator.GenerateCode(host, catalog.DomainServiceDescriptions, options); ConsoleLogger consoleLogger = logger as ConsoleLogger; string errors = consoleLogger == null ? "" : consoleLogger.Errors; Assert.IsTrue(generatedCode.Length > 0, "No code was generated: " + errors); // Dump the generated code into a file for comparison bool isCSharp = options.Language.Equals("C#", StringComparison.InvariantCultureIgnoreCase); string generatedFileName = Path.Combine(outDataDir, Path.GetFileName(referenceFileName) + ".testgen"); File.WriteAllText(generatedFileName, generatedCode); // TODO: (ron M3) Solve inability to get right MSBuild after checkin // First see if we compile List<string> referenceAssemblies = CompilerHelper.GetSilverlightClientAssemblies(codeGenOptions.RelativeDeployDir); List<string> files = new List<string>(); files.Add(generatedFileName); // Unconditionally force generation of Xml doc comments to catch errors string documentationFile = Path.GetTempFileName(); try { if (isCSharp) { files.AddRange(codeGenOptions.SharedFiles.Where(sharedFile => Path.GetExtension(sharedFile).Equals(".cs"))); CompilerHelper.CompileCSharpSource(files, referenceAssemblies, documentationFile); } else { files.AddRange(codeGenOptions.SharedFiles.Where(sharedFile => Path.GetExtension(sharedFile).Equals(".vb"))); CompilerHelper.CompileVisualBasicSource(files, referenceAssemblies, options.ClientRootNamespace, documentationFile); } } finally { File.Delete(documentationFile); } // Do the diff if (codeGenOptions.FailOnDiff) { TestHelper.ValidateFilesEqual(codeGenOptions.RelativeTestDir, codeGenOptions.RelativeDeployDir, generatedFileName, referenceFileName, codeGenOptions.Language); } else { TestHelper.FilesMatch(codeGenOptions.RelativeTestDir, codeGenOptions.RelativeDeployDir, generatedFileName, referenceFileName, codeGenOptions.Language, out diffMessage); } return diffMessage; }
/// <summary> /// Generates client proxy source code using the specified <paramref name="codeGeneratorName"/> in the context /// of the specified <paramref name="host"/>. /// </summary> /// <param name="host">The host for code generation.</param> /// <param name="options">The options to use for code generation.</param> /// <param name="domainServiceTypes">The set of <see cref="OpenRiaServices.DomainServices.Server.DomainService"/> types for which to generate code.</param> /// <param name="compositionAssemblies">The optional set of assemblies to use to create the MEF composition container.</param> /// <param name="codeGeneratorName">Optional generator name. A <c>null</c> or empty value will select the default generator.</param> /// <returns>The generated source code or <c>null</c> if none was generated.</returns> internal string GenerateCode(ICodeGenerationHost host, ClientCodeGenerationOptions options, IEnumerable<Type> domainServiceTypes, IEnumerable<string> compositionAssemblies, string codeGeneratorName) { Debug.Assert(host != null, "host cannot be null"); Debug.Assert(options != null, "options cannot be null"); Debug.Assert(domainServiceTypes != null, "domainServiceTypes cannot be null"); ILogger logger = host as ILogger; DomainServiceCatalog catalog = new DomainServiceCatalog(domainServiceTypes, logger); return this.GenerateCode(host, options, catalog, compositionAssemblies, codeGeneratorName); }
/// <summary> /// Locates and returns the <see cref="IDomainServiceClientCodeGenerator"/> to use to generate client proxies /// for the specified <paramref name="options"/>. /// </summary> /// <param name="host">The host for code generation.</param> /// <param name="options">The options to use for code generation.</param> /// <param name="compositionAssemblies">The optional set of assemblies to use to create the MEF composition container.</param> /// <param name="codeGeneratorName">Optional generator name. A <c>null</c> or empty value will select the default generator.</param> /// <returns>The code generator to use, or <c>null</c> if a matching one could not be found.</returns> internal IDomainServiceClientCodeGenerator FindCodeGenerator(ICodeGenerationHost host, ClientCodeGenerationOptions options, IEnumerable <string> compositionAssemblies, string codeGeneratorName) { Debug.Assert(host != null, "host cannot be null"); Debug.Assert(options != null, "options cannot be null"); if (string.IsNullOrEmpty(options.Language)) { throw new ArgumentException(Resource.Null_Language_Property, nameof(options)); } IDomainServiceClientCodeGenerator generator = null; // Try to load the code generator directly if given an assembly qualified name. // We insist on at least one comma in the name to know this is an assembly qualified name. // Otherwise, we might succeed in loading a dotted name that happens to be in our assembly, // such as the default CodeDom generator. if (!string.IsNullOrEmpty(codeGeneratorName) && codeGeneratorName.Contains(',')) { Type codeGeneratorType = Type.GetType(codeGeneratorName, /*throwOnError*/ false); if (codeGeneratorType != null) { if (!typeof(IDomainServiceClientCodeGenerator).IsAssignableFrom(codeGeneratorType)) { // If generator is of the incorrect type, we will still allow the MEF approach below // to find a better one. This path could be exercised by inadvertantly using a name // that happened to load some random type that was not a code generator. host.LogWarning(string.Format(CultureInfo.CurrentCulture, Resource.Code_Generator_Incorrect_Type, codeGeneratorName)); } else { try { generator = Activator.CreateInstance(codeGeneratorType) as IDomainServiceClientCodeGenerator; } catch (Exception e) { // The open catch of Exception is acceptable because we unconditionally report // the error and are running in a separate AppDomain. if (e.IsFatal()) { throw; } host.LogError(string.Format(CultureInfo.CurrentCulture, Resource.Code_Generator_Instantiation_Error, codeGeneratorName, e.Message)); } } } } if (generator == null) { // Create the MEF composition container (once only) from the assemblies we are analyzing this.CreateCompositionContainer(compositionAssemblies, host as ILogger); // The following property is filled by MEF by the line above. if (this.DomainServiceClientCodeGenerators != null && this.DomainServiceClientCodeGenerators.Any()) { // Select only those registered for the required language IEnumerable <Lazy <IDomainServiceClientCodeGenerator, ICodeGeneratorMetadata> > allImportsForLanguage = this.DomainServiceClientCodeGenerators.Where(i => string.Equals(options.Language, i.Metadata.Language, StringComparison.OrdinalIgnoreCase)); Lazy <IDomainServiceClientCodeGenerator, ICodeGeneratorMetadata> lazyImport = null; // If client specified a specific generator, use that one. // If it cannot be found, log an error to explain the problem. // If multiple with that name are found, log an error and explain the problem. // We consider this an error because the user has explicitly named a generator, // meaning they would not expect the default to be used. if (!string.IsNullOrEmpty(codeGeneratorName)) { IEnumerable <Lazy <IDomainServiceClientCodeGenerator, ICodeGeneratorMetadata> > allImportsForLanguageAndName = allImportsForLanguage.Where(i => string.Equals(i.Metadata.GeneratorName, codeGeneratorName, StringComparison.OrdinalIgnoreCase)); int numberOfMatchingGenerators = allImportsForLanguageAndName.Count(); // No generator with that name was found. Log an error and explain how to register one. if (numberOfMatchingGenerators == 0) { host.LogError(string.Format(CultureInfo.CurrentCulture, Resource.Code_Generator_Not_Found, codeGeneratorName, options.Language, options.ServerProjectPath, options.ClientProjectPath, CodeDomClientCodeGenerator.GeneratorName)); } else if (numberOfMatchingGenerators == 1) { // Exactly one was found -- take it lazyImport = allImportsForLanguageAndName.First(); } else { // Multiple with that name were found. Explain how to remove some of them or // explicitly name one. StringBuilder sb = new StringBuilder(); foreach (var import in allImportsForLanguageAndName.OrderBy(i => i.Value.GetType().FullName)) { sb.AppendLine(" " + import.Value.GetType().FullName); } host.LogError(string.Format(CultureInfo.CurrentCulture, Resource.Multiple_Named_Code_Generators, codeGeneratorName, options.Language, sb.ToString(), options.ServerProjectPath, options.ClientProjectPath, allImportsForLanguageAndName.First().Value.GetType().AssemblyQualifiedName)); } } else { // We are here if no generator name was specified. // If only one import matched the language, we have it. // This is the most common path to discovery of our own CodeDom generator // but will work equally well when it replaced. if (allImportsForLanguage.Count() == 1) { lazyImport = allImportsForLanguage.First(); } else { // Multiple custom generators exist, but a specific generator name was not provided. // Look for any custom generators other than our default CodeDom one. // If we find there is only one custom generator registered, we use that one rather than the default IEnumerable <Lazy <IDomainServiceClientCodeGenerator, ICodeGeneratorMetadata> > customGeneratorImports = allImportsForLanguage.Where(i => !string.Equals(CodeDomClientCodeGenerator.GeneratorName, i.Metadata.GeneratorName, StringComparison.OrdinalIgnoreCase)); int generatorCount = customGeneratorImports.Count(); // Exactly 1 custom generator that is not the default -- take it if (generatorCount == 1) { lazyImport = customGeneratorImports.First(); host.LogMessage(string.Format(CultureInfo.CurrentCulture, Resource.Using_Custom_Code_Generator, lazyImport.Metadata.GeneratorName)); } else if (generatorCount != 0) { // Multiple generators are available but we have insufficient information // to choose one. Log an warning and use the default StringBuilder sb = new StringBuilder(); // Sort for unit test predictability IEnumerable <Lazy <IDomainServiceClientCodeGenerator, ICodeGeneratorMetadata> > orderedCustomGenerators = customGeneratorImports.OrderBy(i => i.Metadata.GeneratorName); foreach (var import in orderedCustomGenerators) { sb.AppendLine(" " + import.Metadata.GeneratorName); } host.LogWarning(string.Format(CultureInfo.CurrentCulture, Resource.Multiple_Custom_Code_Generators_Using_Default, options.Language, sb.ToString(), options.ClientProjectPath, orderedCustomGenerators.First().Metadata.GeneratorName, CodeDomClientCodeGenerator.GeneratorName)); // Pick the default. There should be one, but if not, the calling methods will detect and report a problem. lazyImport = allImportsForLanguage.FirstOrDefault(i => string.Equals(CodeDomClientCodeGenerator.GeneratorName, i.Metadata.GeneratorName, StringComparison.OrdinalIgnoreCase)); } } } generator = lazyImport == null ? null : lazyImport.Value; } } return(generator); }
// Invokes the code generator discovered via the host and options internal static string GenerateCode(ICodeGenerationHost host, ClientCodeGenerationOptions options, IEnumerable<Type> domainServiceTypes) { IDomainServiceClientCodeGenerator generator = CreateCodeGenerator(host, options); DomainServiceCatalog catalog = new DomainServiceCatalog(domainServiceTypes, host as ILogger); return generator.GenerateCode(host, catalog.DomainServiceDescriptions, options); }
/// <summary> /// Generates client proxy source code using the specified <paramref name="codeGeneratorName"/> in the context /// of the specified <paramref name="host"/>. /// </summary> /// <param name="host">The host for code generation.</param> /// <param name="options">The options to use for code generation.</param> /// <param name="assembliesToLoad">The set of server assemblies to use for analysis and composition.</param> /// <param name="codeGeneratorName">Optional generator name. A <c>null</c> or empty value will select the default generator.</param> /// <returns>The generated source code or <c>null</c> if none was generated.</returns> internal string GenerateCode(ICodeGenerationHost host, ClientCodeGenerationOptions options, IEnumerable<string> assembliesToLoad, string codeGeneratorName) { Debug.Assert(host != null, "host cannot be null"); Debug.Assert(options != null, "options cannot be null"); Debug.Assert(assembliesToLoad != null, "assembliesToLoad cannot be null"); ILogger logger = host as ILogger; DomainServiceCatalog catalog = new DomainServiceCatalog(assembliesToLoad, logger); return this.GenerateCode(host, options, catalog, assembliesToLoad, codeGeneratorName); }