/// <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);
        }
Example #2
0
        public string GenerateCode(ICodeGenerationHost host, IEnumerable <DomainServiceDescription> descriptions, ClientCodeGenerationOptions options)
        {
            Assert.IsNotNull(host, "host cannot be null when code generator is called");
            Assert.IsNotNull(options, "options cannot be null when code generator is called");
            Assert.IsNotNull(descriptions, "descriptions cannot be null when code generator is called");

            // These 2 test helpers reset each time they are read
            bool logWarningsFull = MockCodeGenerator.LogWarningsFull;

            MockCodeGenerator.LogWarningsFull = false;

            bool logErrorsFull = MockCodeGenerator.LogErrorsFull;

            MockCodeGenerator.LogErrorsFull = false;

            bool throwException = MockCodeGenerator.ThrowException;

            MockCodeGenerator.ThrowException = false;

            if (throwException)
            {
                throw MockCodeGenerator.Exception;
            }

            if (logWarningsFull)
            {
                ConsoleLogger.LogPacket p = MockCodeGenerator.WarningPacket;
                host.LogWarning(p.Message, p.Subcategory, p.ErrorCode, p.HelpString, p.File, p.LineNumber, p.ColumnNumber, p.EndLineNumber, p.EndColumnNumber);
            }
            if (logErrorsFull)
            {
                ConsoleLogger.LogPacket p = MockCodeGenerator.ErrorPacket;
                host.LogError(p.Message, p.Subcategory, p.ErrorCode, p.HelpString, p.File, p.LineNumber, p.ColumnNumber, p.EndLineNumber, p.EndColumnNumber);
            }

            return(MockCodeGenerator.FakeGeneratedCode);
        }
        /// <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);
        }
        public string GenerateCode(ICodeGenerationHost host, IEnumerable<DomainServiceDescription> descriptions, ClientCodeGenerationOptions options)
        {
            Assert.IsNotNull(host, "host cannot be null when code generator is called");
            Assert.IsNotNull(options, "options cannot be null when code generator is called");
            Assert.IsNotNull(descriptions, "descriptions cannot be null when code generator is called");

            // These 2 test helpers reset each time they are read
            bool logWarningsFull = MockCodeGenerator.LogWarningsFull;
            MockCodeGenerator.LogWarningsFull = false;

            bool logErrorsFull = MockCodeGenerator.LogErrorsFull;
            MockCodeGenerator.LogErrorsFull = false;

            bool throwException = MockCodeGenerator.ThrowException;
            MockCodeGenerator.ThrowException = false;

            if (throwException)
            {
                throw MockCodeGenerator.Exception;
            }

            if (logWarningsFull)
            {
                ConsoleLogger.LogPacket p = MockCodeGenerator.WarningPacket;
                host.LogWarning(p.Message, p.Subcategory, p.ErrorCode, p.HelpString, p.File, p.LineNumber, p.ColumnNumber, p.EndLineNumber, p.EndColumnNumber);
            }
            if (logErrorsFull)
            {
                ConsoleLogger.LogPacket p = MockCodeGenerator.ErrorPacket;
                host.LogError(p.Message, p.Subcategory, p.ErrorCode, p.HelpString, p.File, p.LineNumber, p.ColumnNumber, p.EndLineNumber, p.EndColumnNumber);
            }

            return MockCodeGenerator.FakeGeneratedCode;
        }
        /// <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>
        /// 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;
        }