WCFBuildProvider.cs source code in C# .NET

Source code for the .NET framework in C#

                        

Code:

/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / ndp / fx / src / xsp / System / Web / Extensions / Compilation / WCFBuildProvider.cs / 1 / WCFBuildProvider.cs

                            //------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

namespace System.Web.Compilation 
{ 

    using System; 
    using System.Globalization;
    using System.CodeDom;
    using System.CodeDom.Compiler;
    using System.Collections.Specialized; 
    using System.Collections;
    using System.Diagnostics.CodeAnalysis; 
    using System.Net; 
    using System.Xml.Serialization;
    using System.Web.Hosting; 
    using System.Web.UI;
    using System.Diagnostics;
    using System.Threading;
    using System.Text; 
    using System.Web.Compilation.WCFModel;
    using System.Collections.Generic; 
    using System.Web.Configuration; 
    using System.Web.Resources;
    using System.Security.Permissions; 
    using System.Data.Services.Design;
    using System.IO;
    using System.Xml;
    using System.Reflection; 

 
    ///  
    /// A build provider for WCF service references in ASP.NET projects.
    ///   (Note: the ASMX version is called WebReferencesBuildProvider). 
    /// Due to compatibility requirements, as few changes as possible were made
    ///   to System.Web.dll, which contains the build manager and WebReferencesBuildProvider.
    ///   WebReferencesBuildProvider will call into us for the App_WebReferences folder and
    ///   each of its subdirectories (recursively) if it finds our assembly and type on the 
    ///   machine.
    /// Note: for a normal BuildProvider, the input file is represented by the protected VirtualPath 
    ///   property of the base.  But for this build provider (same as for WebReferencesBuildProvider, the 
    ///   asmx web references build provider), the input is an entire directory, which is still represented
    ///   by the protected VirtualPath property. 
    /// TO DEBUG: The easiest way to debug is to debug the aspnet_compiler.exe command-line tool
    ///   while it compiles a website with a .svcmap file in it.
    ///   As an example, you could debug aspnet_compiler.exe with this command-line to debug
    ///   one of the suites: 
    ///
    ///     /v MiddleService -p {path}\ddsuites\src\vs\vb\IndigoTools\BuildProvider\WCFBuildProvider1\WebSite\MiddleService -c c:\temp\output 
    /// 
    ///   Important: it will only call the build provider if the sources in the website have changed or if you delete
    ///   the deleted output folder's contents. 
    ///
    /// Data services (Astoria): in order to support Astoria "data services" we added code
    /// to scan for "datasvcmap" files in addition to the existing "svcmap" files. For data services
    /// we call into the Astoria code-gen library to do the work instead of the regular indigo path. 
    ///
    ///  
    [ 
    AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal),
    AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), 
    PermissionSet(SecurityAction.LinkDemand, Name="FullTrust"),
    SuppressMessage("Microsoft.Naming", "CA1705:LongAcronymsShouldBePascalCased",
                    Justification = "Too late to change in Orcas--the WCFBuildProvider is hard-coded in ASP.NET " +
                                   "build manager for app_webreferences folder. Build manager is part of redbits.") 
    ]
    public class WCFBuildProvider : BuildProvider 
    { 
        internal const string WebRefDirectoryName = "App_WebReferences";
        internal const string SvcMapExtension = ".svcmap"; 
        internal const string DataSvcMapExtension = ".datasvcmap";
        private const string TOOL_CONFIG_ITEM_NAME = "Reference.config";

        ///  
        /// version number for 3.5 framework
        ///  
        private const int FRAMEWORK_VERSION_35 = 0x30005; 

        ///  
        /// Search through the folder represented by base.VirtualPath for .svcmap and .datasvcmap files.
        /// If any .svcmap/.datasvcmap files are found, then generate proxy code for them into the
        /// specified assemblyBuilder.
        ///  
        /// Where to generate the proxy code
        ///  
        /// When this routine is called, it is expected that the protected VirtualPath property has 
        ///   been set to the folder to scan for .svcmap/.datasvcmap files.
        ///  
        public override void GenerateCode(AssemblyBuilder assemblyBuilder)
        {

            // Go through all the svcmap files in the directory 
            VirtualDirectory vdir = GetVirtualDirectory(VirtualPath);
            foreach (VirtualFile child in vdir.Files) 
            { 
                string extension = IO.Path.GetExtension(child.VirtualPath);
                if (extension.Equals(SvcMapExtension, StringComparison.OrdinalIgnoreCase)) 
                {
                    // .svcmap file found

                    // NOTE: the WebReferences code requires a physical path, so this feature 
                    // cannot work with a non-file based VirtualPathProvider
                    string physicalPath = HostingEnvironment.MapPath(child.VirtualPath); 
 
                    CodeCompileUnit codeUnit = GenerateCodeFromServiceMapFile(physicalPath);
 
                    // Add the CodeCompileUnit to the compilation
                    assemblyBuilder.AddCodeCompileUnit(this, codeUnit);
                }
                else if (extension.Equals(DataSvcMapExtension, StringComparison.OrdinalIgnoreCase)) 
                {
                    // NOTE: the WebReferences code requires a physical path, so this feature 
                    // cannot work with a non-file based VirtualPathProvider 
                    string physicalPath = HostingEnvironment.MapPath(child.VirtualPath);
 
                    GenerateCodeFromDataServiceMapFile(physicalPath, assemblyBuilder);
                }
            }
        } 

        ///  
        /// Generate code for one .datasvcmap file 
        /// 
        /// The physical path to the data service map file 
        private void GenerateCodeFromDataServiceMapFile(string mapFilePath, AssemblyBuilder assemblyBuilder)
        {
            try
            { 
                assemblyBuilder.AddAssemblyReference(typeof(System.Data.Services.Client.DataServiceContext).Assembly);
 
                DataSvcMapFileLoader loader = new DataSvcMapFileLoader(mapFilePath); 
                DataSvcMapFile mapFile = loader.LoadMapFile(System.IO.Path.GetFileName(mapFilePath));
 
                if (mapFile.MetadataList[0].ErrorInLoading != null)
                {
                    throw mapFile.MetadataList[0].ErrorInLoading;
                } 

                string edmxContent = mapFile.MetadataList[0].Content; 
 
                System.Data.Services.Design.EntityClassGenerator generator = new System.Data.Services.Design.EntityClassGenerator(LanguageOption.GenerateCSharpCode);
 
                // the EntityClassGenerator works on streams/writers, does not return a CodeDom
                // object, so we use CreateCodeFile instead of compile units.
                using (TextWriter writer = assemblyBuilder.CreateCodeFile(this))
                { 

                    // Note: currently GenerateCode never actually returns values 
                    // for the error case (even though it returns an IList of error 
                    // objects). Instead it throws on error. This may need some tweaking
                    // later on. 
#if DEBUG
                object errors =
#endif
                    generator.GenerateCode( 
                        XmlReader.Create(new StringReader(edmxContent)),
                        writer, 
                        GetGeneratedNamespace()); 

#if DEBUG 
                Debug.Assert(
                    errors == null ||
                    !(errors is ICollection) ||
                    ((ICollection)errors).Count == 0, 
                    "Errors reported through the return value. Expected an exception");
#endif 
                    writer.Flush(); 
                }
            } 
            catch (Exception ex)
            {
                string errorMessage = ex.Message;
                errorMessage = String.Format(CultureInfo.CurrentCulture, "{0}: {1}", IO.Path.GetFileName(mapFilePath), errorMessage); 
                throw new InvalidOperationException(errorMessage, ex);
            } 
        } 

        ///  
        /// Generate code for one .svcmap file
        /// 
        /// the path to the service map file
        ///  
        /// 
        private CodeCompileUnit GenerateCodeFromServiceMapFile(string mapFilePath) 
        { 
            try
            { 
                string generatedNamespace = GetGeneratedNamespace();
                SvcMapFileLoader loader = new SvcMapFileLoader(mapFilePath);
                SvcMapFile mapFile = loader.LoadMapFile(System.IO.Path.GetFileName(mapFilePath));
 
                HandleProxyGenerationErrors(mapFile.LoadErrors);
 
                // We always use C# for the generated proxy 
                CodeDomProvider provider = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("c#");
 
                //Note: with the current implementation of the generator, it does all of its
                //  work in the constructor.  This may change in the future.
                VSWCFServiceContractGenerator generator = VSWCFServiceContractGenerator.GenerateCodeAndConfiguration(
                    mapFile, 
                    GetToolConfig(mapFile, mapFilePath),
                    provider, 
                    generatedNamespace, 
                    null, //targetConfiguration
                    null, //configurationNamespace 
                    new ImportExtensionServiceProvider(),
                    new TypeResolver(),
                    FRAMEWORK_VERSION_35,
                    typeof (System.Data.Design.TypedDataSetSchemaImporterExtensionFx35) //Always we are above framework version 3.5 
                );
 
                // Determine what "name" to display to users for the service if there are any exceptions 
                // If generatedNamespace is empty, then we display the name of the .svcmap file.
                string referenceDisplayName = String.IsNullOrEmpty(generatedNamespace) ? 
                    System.IO.Path.GetFileName(mapFilePath) : generatedNamespace;

                VerifyGeneratedCodeAndHandleErrors(referenceDisplayName, mapFile, generator.TargetCompileUnit, generator.ImportErrors, generator.ProxyGenerationErrors);
#if DEBUG 
#if false
                IO.TextWriter writer = new IO.StringWriter(); 
                CodeGeneratorOptions options = new CodeGeneratorOptions(); 
                options.BlankLinesBetweenMembers=true;
                provider.GenerateCodeFromCompileUnit(generator.TargetCompileUnit, writer, options); 
                Debug.WriteLine("Generated proxy code:\r\n" + writer.ToString());
#endif
#endif
                return generator.TargetCompileUnit; 
            }
            catch (Exception ex) 
            { 
                string errorMessage = ex.Message;
                errorMessage = String.Format(CultureInfo.CurrentCulture, "{0}: {1}", IO.Path.GetFileName(mapFilePath), errorMessage); 
                throw new InvalidOperationException(errorMessage, ex);
            }
        }
 
        /// 
        /// If there are errors passed in, handle them by throwing an appropriate error 
        ///  
        /// IEnumerable for easier unit test accessors
        private static void HandleProxyGenerationErrors(System.Collections.IEnumerable /**/ errors) 
        {
            foreach (ProxyGenerationError generationError in errors)
            {
                // NOTE: the ASP.Net framework does not handle an error list, so we only give them the first error message 
                // all warning messages are ignored today
                // 
                // We treat all error messages from WsdlImport and ProxyGenerator as warning messages 
                //   The reason is that many of them are ignorable and doesn't block generating useful code.
                if (!generationError.IsWarning && 
                        generationError.ErrorGeneratorState != WCFModel.ProxyGenerationError.GeneratorState.GenerateCode)
                {
                    throw new InvalidOperationException(ConvertToBuildProviderErrorMessage(generationError));
                } 
            }
        } 
 
        /// 
        /// Merge error message strings together 
        /// 
        /// 
        /// 
        ///  
        /// 
        private static void CollectErrorMessages(System.Collections.IEnumerable errors, StringBuilder collectedMessages) 
        { 
            foreach (ProxyGenerationError generationError in errors)
            { 
                if (!generationError.IsWarning)
                {
                    if (collectedMessages.Length > 0)
                    { 
                        collectedMessages.Append(Environment.NewLine);
                    } 
                    collectedMessages.Append(ConvertToBuildProviderErrorMessage(generationError)); 
                }
            } 
        }

        /// 
        /// Format an error reported by the code generator to message string reported to the user. 
        /// 
        ///  
        ///  
        /// 
        private static string ConvertToBuildProviderErrorMessage(ProxyGenerationError generationError) 
        {
            string errorMessage = generationError.Message;
            if (!String.IsNullOrEmpty(generationError.MetadataFile))
            { 
                if (generationError.LineNumber < 0)
                { 
                    errorMessage = String.Format(CultureInfo.CurrentCulture, "'{0}': {1}", generationError.MetadataFile, errorMessage); 
                }
                else if (generationError.LinePosition < 0) 
                {
                    errorMessage = String.Format(CultureInfo.CurrentCulture, "'{0}' ({1}): {2}", generationError.MetadataFile, generationError.LineNumber, errorMessage);
                }
                else 
                {
                    errorMessage = String.Format(CultureInfo.CurrentCulture, "'{0}' ({1},{2}): {3}", generationError.MetadataFile, generationError.LineNumber, generationError.LinePosition, errorMessage); 
                } 
            }
            return errorMessage; 
        }

        /// 
        /// Check the result from the code generator. 
        /// By default we treat all error messages from WsdlImporter as warnings,
        /// and they will be ignored if valid code has been generated. 
        /// We may hit other errors when we parse the metadata files. 
        /// Those errors (which are usually because of a bad file) will not be ignored, because the user can fix them.
        /// If the WsdlImporter hasn't generated any code as we expect, we have to consider some of the error messages are fatal. 
        /// We collect those messages and report to the user.
        /// 
        /// The name of the generated reference
        /// Original Map File 
        /// generated code compile unit
        ///  
        ///  
        /// 
        private static void VerifyGeneratedCodeAndHandleErrors( 
                                            string referenceDisplayName,
                                            SvcMapFile mapFile,
                                            CodeCompileUnit generatedCode,
                                            System.Collections.IEnumerable importErrors, 
                                            System.Collections.IEnumerable generatorErrors)
        { 
            // Check and report fatal error first... 
            HandleProxyGenerationErrors(importErrors);
            HandleProxyGenerationErrors(generatorErrors); 

            // if there is no fatal error, we expect valid type generated from the process
            //   unless there is no metadata files, or there is a service contract type sharing
            if (mapFile.MetadataList.Count > 0 && mapFile.ClientOptions.ServiceContractMappingList.Count == 0) 
            {
                if (!IsAnyTypeGenerated(generatedCode)) 
                { 
                    StringBuilder collectedMessages = new StringBuilder();
 
                    // merge error messages
                    CollectErrorMessages(importErrors, collectedMessages);
                    CollectErrorMessages(generatorErrors, collectedMessages);
 
                    throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, WCFModelStrings.ReferenceGroup_FailedToGenerateCode, referenceDisplayName, collectedMessages.ToString()));
                } 
            } 
        }
 
        /// 
        /// Check whether we have generated any types
        /// 
        ///  
        /// 
        ///  
        private static bool IsAnyTypeGenerated(CodeCompileUnit compileUnit) 
        {
            if (compileUnit != null) 
            {
                foreach (System.CodeDom.CodeNamespace codeNamespace in compileUnit.Namespaces)
                {
                    if (codeNamespace.Types.Count > 0) 
                    {
                        return true; 
                    } 
                }
            } 
            return false;
        }

        ///  
        ///  Retrieve a VirtualDirectory for the given virtual path
        ///  
        ///  
        /// 
        private VirtualDirectory GetVirtualDirectory(string virtualPath) 
        {
            return HostingEnvironment.VirtualPathProvider.GetDirectory(VirtualPath);
        }
 
        /// 
        ///  Caculate our namespace for current VirtualPath... 
        ///  
        /// 
        ///  
        private string GetGeneratedNamespace()
        {
            // First, determine the namespace to use for code generation.  This is based on the
            //   relative path of the reference from its base App_WebReferences directory 

            // ... Get the virtual path to the App_WebReferences folder, e.g "/MyApp/App_WebReferences" 
            string rootWebRefDirVirtualPath = GetWebRefDirectoryVirtualPath(); 

            // ... Get the folder's directory path, e.g "/MyApp/Application_WebReferences/Foo/Bar", 
            //     where we'll look for .svcmap files
            string currentSubfolderUnderWebReferences = this.VirtualPath;
            if (currentSubfolderUnderWebReferences == null)
            { 
                Debug.Fail("Shouldn't be given a null virtual path");
                throw new InvalidOperationException(); 
            } 

            return CalculateGeneratedNamespace(rootWebRefDirVirtualPath, currentSubfolderUnderWebReferences); 
        }

        /// 
        /// Determine the namespace to use for the proxy generation 
        /// 
        /// The path to the App_WebReferences folder 
        /// The path to the current folder 
        /// 
        private static string CalculateGeneratedNamespace(string webReferencesRootVirtualPath, string virtualPath) 
        {
            // ... Ensure both folders have trailing slashes
            webReferencesRootVirtualPath = VirtualPathUtility.AppendTrailingSlash(webReferencesRootVirtualPath);
            virtualPath = VirtualPathUtility.AppendTrailingSlash(virtualPath); 

            Debug.Assert(virtualPath.StartsWith(webReferencesRootVirtualPath, StringComparison.OrdinalIgnoreCase), 
                "We expected to be inside the App_WebReferences folder"); 

            // ... Determine the namespace to use, based on the directory structure where the .svcmap file 
            //     is found.
            if (webReferencesRootVirtualPath.Length == virtualPath.Length)
            {
                Debug.Assert(string.Equals(webReferencesRootVirtualPath, virtualPath, StringComparison.OrdinalIgnoreCase), 
                    "We expected to be in the App_WebReferences directory");
 
                // If it's the root WebReferences dir, use the empty namespace 
                return String.Empty;
            } 
            else
            {
                // We're in a subdirectory of App_WebReferences.
                // Get the directory's relative path from App_WebReferences, e.g. "Foo/Bar" 

                virtualPath = VirtualPathUtility.RemoveTrailingSlash(virtualPath).Substring(webReferencesRootVirtualPath.Length); 
 
                // Split it into chunks separated by '/'
                string[] chunks = virtualPath.Split('/'); 

                // Turn all the relevant chunks into valid namespace chunks
                for (int i = 0; i < chunks.Length; i++)
                { 
                    chunks[i] = MakeValidTypeNameFromString(chunks[i]);
                } 
 
                // Put the relevant chunks back together to form the namespace
                return String.Join(".", chunks); 
            }
        }

        ///  
        /// Returns the app domain's application virtual path [from HttpRuntime.AppDomainAppVPath].
        /// Includes trailing slash, e.g. "/MyApp/" 
        ///  
        private static string GetAppDomainAppVirtualPath()
        { 
            string appVirtualPath = HttpRuntime.AppDomainAppVirtualPath;
            if (appVirtualPath == null)
            {
                Debug.Fail("Shouldn't get a null app virtual path from the app domain"); 
                throw new InvalidOperationException();
            } 
 
            return VirtualPathUtility.AppendTrailingSlash(VirtualPathUtility.ToAbsolute(appVirtualPath));
        } 

        /// 
        /// Gets the virtual path to the application's App_WebReferences directory, e.g. "/MyApp/App_WebReferences/"
        ///  
        private static string GetWebRefDirectoryVirtualPath()
        { 
            return VirtualPathUtility.Combine(GetAppDomainAppVirtualPath(), WebRefDirectoryName + @"\"); 
        }
 
        /// 
        /// Return a valid type name from a string by changing any character
        ///   that's not a letter or a digit to an '_'.
        ///  
        /// 
        ///  
        internal static string MakeValidTypeNameFromString(string typeName) 
        {
            if (String.IsNullOrEmpty(typeName)) 
                throw new ArgumentNullException("typeName");

            StringBuilder sb = new StringBuilder();
 
            for (int i = 0; i < typeName.Length; i++)
            { 
                // Make sure it doesn't start with a digit (ASURT 31134) 
                if (i == 0 && Char.IsDigit(typeName[0]))
                    sb.Append('_'); 

                if (Char.IsLetterOrDigit(typeName[i]))
                    sb.Append(typeName[i]);
                else 
                    sb.Append('_');
            } 
 
            // Identifier can't be a single underscore character
            string validTypeName = sb.ToString(); 
            if (validTypeName.Equals("_", StringComparison.Ordinal))
            {
                validTypeName = "__";
            } 

            return validTypeName; 
        } 

        ///  
        /// Get the appropriate tool configuration for this service reference.
        ///
        /// If a reference.config file is present, the configuration object returned
        /// will be the merged view of: 
        ///
        ///    Machine Config 
        ///       ReferenceConfig 
        ///
        /// If not reference.config file is present, the configuration object returned 
        /// will be a merged view of:
        ///
        ///     Machine.config
        ///         web.config in application's physical path... 
        ///
        ///  
        /// SvcMapFile representing the service 
        /// 
        private System.Configuration.Configuration GetToolConfig(SvcMapFile mapFile, string mapFilePath) 
        {
            string toolConfigFile = null;

            if (mapFile != null && mapFilePath != null) 
            {
                foreach (ExtensionFile extensionFile in mapFile.Extensions) 
                { 
                    if (String.Equals(extensionFile.Name, TOOL_CONFIG_ITEM_NAME, StringComparison.Ordinal))
                    { 
                        toolConfigFile = extensionFile.FileName;
                    }
                }
            } 

            System.Web.Configuration.WebConfigurationFileMap fileMap; 
            fileMap = new System.Web.Configuration.WebConfigurationFileMap(); 

            System.Web.Configuration.VirtualDirectoryMapping mapping; 
            if (toolConfigFile != null)
            {
                //
                // If we've got a specific tool configuration to use, we better load that... 
                //
                mapping = new System.Web.Configuration.VirtualDirectoryMapping(System.IO.Path.GetDirectoryName(mapFilePath), true, toolConfigFile); 
            } 
            else
            { 
                //
                // Otherwise we fall back to the default web.config file...
                //
                mapping = new System.Web.Configuration.VirtualDirectoryMapping(HostingEnvironment.ApplicationPhysicalPath, true); 
            }
            fileMap.VirtualDirectories.Add("/", mapping); 
 
            return System.Web.Configuration.WebConfigurationManager.OpenMappedWebConfiguration(fileMap, "/", System.Web.Hosting.HostingEnvironment.SiteName);
 
        }


        ///  
        /// Helper class to implement type resolution for the generator
        ///  
        private class TypeResolver : IContractGeneratorReferenceTypeLoader 
        {
            private System.Reflection.Assembly[] _referencedAssemblies; 

            private IEnumerable ReferencedAssemblies
            {
                get 
                {
                    if (_referencedAssemblies == null) 
                    { 
                        System.Collections.ICollection referencedAssemblyCollection = BuildManager.GetReferencedAssemblies();
                        _referencedAssemblies = new System.Reflection.Assembly[referencedAssemblyCollection.Count]; 
                        referencedAssemblyCollection.CopyTo(_referencedAssemblies, 0);
                    }
                    return _referencedAssemblies;
                } 
            }
 
            System.Type IContractGeneratorReferenceTypeLoader.LoadType(string typeName) 
            {
                // If the type can't be resolved, we need an exception thrown (thus the true argument) 
                //   so it can be reported as a build error.
                return BuildManager.GetType(typeName, true);
            }
 
            System.Reflection.Assembly IContractGeneratorReferenceTypeLoader.LoadAssembly(string assemblyName)
            { 
                System.Reflection.AssemblyName assemblyToLookFor = new System.Reflection.AssemblyName(assemblyName); 

                foreach (System.Reflection.Assembly assembly in ReferencedAssemblies) 
                {
                    if (System.Reflection.AssemblyName.ReferenceMatchesDefinition(assemblyToLookFor, assembly.GetName()))
                    {
                        return assembly; 
                    }
                } 
 
                throw new System.IO.FileNotFoundException(String.Format(CultureInfo.CurrentCulture, WCFModelStrings.ReferenceGroup_FailedToLoadAssembly, assemblyName));
            } 

            void IContractGeneratorReferenceTypeLoader.LoadAllAssemblies(out IEnumerable loadedAssemblies, out IEnumerable loadingErrors)
            {
                loadedAssemblies = ReferencedAssemblies; 
                loadingErrors = new System.Exception[] {};
            } 
 
        }
 
        private class ImportExtensionServiceProvider : IServiceProvider
        {

            #region IServiceProvider Members 

            public object GetService(Type serviceType) 
            { 
                // We don't currently provide any services to import extensions in the build provider context
                return null; 
            }

            #endregion
        } 

    } 
 
}
 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
//------------------------------------------------------------------------------ 
// 
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// 
//----------------------------------------------------------------------------- 

namespace System.Web.Compilation 
{ 

    using System; 
    using System.Globalization;
    using System.CodeDom;
    using System.CodeDom.Compiler;
    using System.Collections.Specialized; 
    using System.Collections;
    using System.Diagnostics.CodeAnalysis; 
    using System.Net; 
    using System.Xml.Serialization;
    using System.Web.Hosting; 
    using System.Web.UI;
    using System.Diagnostics;
    using System.Threading;
    using System.Text; 
    using System.Web.Compilation.WCFModel;
    using System.Collections.Generic; 
    using System.Web.Configuration; 
    using System.Web.Resources;
    using System.Security.Permissions; 
    using System.Data.Services.Design;
    using System.IO;
    using System.Xml;
    using System.Reflection; 

 
    ///  
    /// A build provider for WCF service references in ASP.NET projects.
    ///   (Note: the ASMX version is called WebReferencesBuildProvider). 
    /// Due to compatibility requirements, as few changes as possible were made
    ///   to System.Web.dll, which contains the build manager and WebReferencesBuildProvider.
    ///   WebReferencesBuildProvider will call into us for the App_WebReferences folder and
    ///   each of its subdirectories (recursively) if it finds our assembly and type on the 
    ///   machine.
    /// Note: for a normal BuildProvider, the input file is represented by the protected VirtualPath 
    ///   property of the base.  But for this build provider (same as for WebReferencesBuildProvider, the 
    ///   asmx web references build provider), the input is an entire directory, which is still represented
    ///   by the protected VirtualPath property. 
    /// TO DEBUG: The easiest way to debug is to debug the aspnet_compiler.exe command-line tool
    ///   while it compiles a website with a .svcmap file in it.
    ///   As an example, you could debug aspnet_compiler.exe with this command-line to debug
    ///   one of the suites: 
    ///
    ///     /v MiddleService -p {path}\ddsuites\src\vs\vb\IndigoTools\BuildProvider\WCFBuildProvider1\WebSite\MiddleService -c c:\temp\output 
    /// 
    ///   Important: it will only call the build provider if the sources in the website have changed or if you delete
    ///   the deleted output folder's contents. 
    ///
    /// Data services (Astoria): in order to support Astoria "data services" we added code
    /// to scan for "datasvcmap" files in addition to the existing "svcmap" files. For data services
    /// we call into the Astoria code-gen library to do the work instead of the regular indigo path. 
    ///
    ///  
    [ 
    AspNetHostingPermission(SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal),
    AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal), 
    PermissionSet(SecurityAction.LinkDemand, Name="FullTrust"),
    SuppressMessage("Microsoft.Naming", "CA1705:LongAcronymsShouldBePascalCased",
                    Justification = "Too late to change in Orcas--the WCFBuildProvider is hard-coded in ASP.NET " +
                                   "build manager for app_webreferences folder. Build manager is part of redbits.") 
    ]
    public class WCFBuildProvider : BuildProvider 
    { 
        internal const string WebRefDirectoryName = "App_WebReferences";
        internal const string SvcMapExtension = ".svcmap"; 
        internal const string DataSvcMapExtension = ".datasvcmap";
        private const string TOOL_CONFIG_ITEM_NAME = "Reference.config";

        ///  
        /// version number for 3.5 framework
        ///  
        private const int FRAMEWORK_VERSION_35 = 0x30005; 

        ///  
        /// Search through the folder represented by base.VirtualPath for .svcmap and .datasvcmap files.
        /// If any .svcmap/.datasvcmap files are found, then generate proxy code for them into the
        /// specified assemblyBuilder.
        ///  
        /// Where to generate the proxy code
        ///  
        /// When this routine is called, it is expected that the protected VirtualPath property has 
        ///   been set to the folder to scan for .svcmap/.datasvcmap files.
        ///  
        public override void GenerateCode(AssemblyBuilder assemblyBuilder)
        {

            // Go through all the svcmap files in the directory 
            VirtualDirectory vdir = GetVirtualDirectory(VirtualPath);
            foreach (VirtualFile child in vdir.Files) 
            { 
                string extension = IO.Path.GetExtension(child.VirtualPath);
                if (extension.Equals(SvcMapExtension, StringComparison.OrdinalIgnoreCase)) 
                {
                    // .svcmap file found

                    // NOTE: the WebReferences code requires a physical path, so this feature 
                    // cannot work with a non-file based VirtualPathProvider
                    string physicalPath = HostingEnvironment.MapPath(child.VirtualPath); 
 
                    CodeCompileUnit codeUnit = GenerateCodeFromServiceMapFile(physicalPath);
 
                    // Add the CodeCompileUnit to the compilation
                    assemblyBuilder.AddCodeCompileUnit(this, codeUnit);
                }
                else if (extension.Equals(DataSvcMapExtension, StringComparison.OrdinalIgnoreCase)) 
                {
                    // NOTE: the WebReferences code requires a physical path, so this feature 
                    // cannot work with a non-file based VirtualPathProvider 
                    string physicalPath = HostingEnvironment.MapPath(child.VirtualPath);
 
                    GenerateCodeFromDataServiceMapFile(physicalPath, assemblyBuilder);
                }
            }
        } 

        ///  
        /// Generate code for one .datasvcmap file 
        /// 
        /// The physical path to the data service map file 
        private void GenerateCodeFromDataServiceMapFile(string mapFilePath, AssemblyBuilder assemblyBuilder)
        {
            try
            { 
                assemblyBuilder.AddAssemblyReference(typeof(System.Data.Services.Client.DataServiceContext).Assembly);
 
                DataSvcMapFileLoader loader = new DataSvcMapFileLoader(mapFilePath); 
                DataSvcMapFile mapFile = loader.LoadMapFile(System.IO.Path.GetFileName(mapFilePath));
 
                if (mapFile.MetadataList[0].ErrorInLoading != null)
                {
                    throw mapFile.MetadataList[0].ErrorInLoading;
                } 

                string edmxContent = mapFile.MetadataList[0].Content; 
 
                System.Data.Services.Design.EntityClassGenerator generator = new System.Data.Services.Design.EntityClassGenerator(LanguageOption.GenerateCSharpCode);
 
                // the EntityClassGenerator works on streams/writers, does not return a CodeDom
                // object, so we use CreateCodeFile instead of compile units.
                using (TextWriter writer = assemblyBuilder.CreateCodeFile(this))
                { 

                    // Note: currently GenerateCode never actually returns values 
                    // for the error case (even though it returns an IList of error 
                    // objects). Instead it throws on error. This may need some tweaking
                    // later on. 
#if DEBUG
                object errors =
#endif
                    generator.GenerateCode( 
                        XmlReader.Create(new StringReader(edmxContent)),
                        writer, 
                        GetGeneratedNamespace()); 

#if DEBUG 
                Debug.Assert(
                    errors == null ||
                    !(errors is ICollection) ||
                    ((ICollection)errors).Count == 0, 
                    "Errors reported through the return value. Expected an exception");
#endif 
                    writer.Flush(); 
                }
            } 
            catch (Exception ex)
            {
                string errorMessage = ex.Message;
                errorMessage = String.Format(CultureInfo.CurrentCulture, "{0}: {1}", IO.Path.GetFileName(mapFilePath), errorMessage); 
                throw new InvalidOperationException(errorMessage, ex);
            } 
        } 

        ///  
        /// Generate code for one .svcmap file
        /// 
        /// the path to the service map file
        ///  
        /// 
        private CodeCompileUnit GenerateCodeFromServiceMapFile(string mapFilePath) 
        { 
            try
            { 
                string generatedNamespace = GetGeneratedNamespace();
                SvcMapFileLoader loader = new SvcMapFileLoader(mapFilePath);
                SvcMapFile mapFile = loader.LoadMapFile(System.IO.Path.GetFileName(mapFilePath));
 
                HandleProxyGenerationErrors(mapFile.LoadErrors);
 
                // We always use C# for the generated proxy 
                CodeDomProvider provider = System.CodeDom.Compiler.CodeDomProvider.CreateProvider("c#");
 
                //Note: with the current implementation of the generator, it does all of its
                //  work in the constructor.  This may change in the future.
                VSWCFServiceContractGenerator generator = VSWCFServiceContractGenerator.GenerateCodeAndConfiguration(
                    mapFile, 
                    GetToolConfig(mapFile, mapFilePath),
                    provider, 
                    generatedNamespace, 
                    null, //targetConfiguration
                    null, //configurationNamespace 
                    new ImportExtensionServiceProvider(),
                    new TypeResolver(),
                    FRAMEWORK_VERSION_35,
                    typeof (System.Data.Design.TypedDataSetSchemaImporterExtensionFx35) //Always we are above framework version 3.5 
                );
 
                // Determine what "name" to display to users for the service if there are any exceptions 
                // If generatedNamespace is empty, then we display the name of the .svcmap file.
                string referenceDisplayName = String.IsNullOrEmpty(generatedNamespace) ? 
                    System.IO.Path.GetFileName(mapFilePath) : generatedNamespace;

                VerifyGeneratedCodeAndHandleErrors(referenceDisplayName, mapFile, generator.TargetCompileUnit, generator.ImportErrors, generator.ProxyGenerationErrors);
#if DEBUG 
#if false
                IO.TextWriter writer = new IO.StringWriter(); 
                CodeGeneratorOptions options = new CodeGeneratorOptions(); 
                options.BlankLinesBetweenMembers=true;
                provider.GenerateCodeFromCompileUnit(generator.TargetCompileUnit, writer, options); 
                Debug.WriteLine("Generated proxy code:\r\n" + writer.ToString());
#endif
#endif
                return generator.TargetCompileUnit; 
            }
            catch (Exception ex) 
            { 
                string errorMessage = ex.Message;
                errorMessage = String.Format(CultureInfo.CurrentCulture, "{0}: {1}", IO.Path.GetFileName(mapFilePath), errorMessage); 
                throw new InvalidOperationException(errorMessage, ex);
            }
        }
 
        /// 
        /// If there are errors passed in, handle them by throwing an appropriate error 
        ///  
        /// IEnumerable for easier unit test accessors
        private static void HandleProxyGenerationErrors(System.Collections.IEnumerable /**/ errors) 
        {
            foreach (ProxyGenerationError generationError in errors)
            {
                // NOTE: the ASP.Net framework does not handle an error list, so we only give them the first error message 
                // all warning messages are ignored today
                // 
                // We treat all error messages from WsdlImport and ProxyGenerator as warning messages 
                //   The reason is that many of them are ignorable and doesn't block generating useful code.
                if (!generationError.IsWarning && 
                        generationError.ErrorGeneratorState != WCFModel.ProxyGenerationError.GeneratorState.GenerateCode)
                {
                    throw new InvalidOperationException(ConvertToBuildProviderErrorMessage(generationError));
                } 
            }
        } 
 
        /// 
        /// Merge error message strings together 
        /// 
        /// 
        /// 
        ///  
        /// 
        private static void CollectErrorMessages(System.Collections.IEnumerable errors, StringBuilder collectedMessages) 
        { 
            foreach (ProxyGenerationError generationError in errors)
            { 
                if (!generationError.IsWarning)
                {
                    if (collectedMessages.Length > 0)
                    { 
                        collectedMessages.Append(Environment.NewLine);
                    } 
                    collectedMessages.Append(ConvertToBuildProviderErrorMessage(generationError)); 
                }
            } 
        }

        /// 
        /// Format an error reported by the code generator to message string reported to the user. 
        /// 
        ///  
        ///  
        /// 
        private static string ConvertToBuildProviderErrorMessage(ProxyGenerationError generationError) 
        {
            string errorMessage = generationError.Message;
            if (!String.IsNullOrEmpty(generationError.MetadataFile))
            { 
                if (generationError.LineNumber < 0)
                { 
                    errorMessage = String.Format(CultureInfo.CurrentCulture, "'{0}': {1}", generationError.MetadataFile, errorMessage); 
                }
                else if (generationError.LinePosition < 0) 
                {
                    errorMessage = String.Format(CultureInfo.CurrentCulture, "'{0}' ({1}): {2}", generationError.MetadataFile, generationError.LineNumber, errorMessage);
                }
                else 
                {
                    errorMessage = String.Format(CultureInfo.CurrentCulture, "'{0}' ({1},{2}): {3}", generationError.MetadataFile, generationError.LineNumber, generationError.LinePosition, errorMessage); 
                } 
            }
            return errorMessage; 
        }

        /// 
        /// Check the result from the code generator. 
        /// By default we treat all error messages from WsdlImporter as warnings,
        /// and they will be ignored if valid code has been generated. 
        /// We may hit other errors when we parse the metadata files. 
        /// Those errors (which are usually because of a bad file) will not be ignored, because the user can fix them.
        /// If the WsdlImporter hasn't generated any code as we expect, we have to consider some of the error messages are fatal. 
        /// We collect those messages and report to the user.
        /// 
        /// The name of the generated reference
        /// Original Map File 
        /// generated code compile unit
        ///  
        ///  
        /// 
        private static void VerifyGeneratedCodeAndHandleErrors( 
                                            string referenceDisplayName,
                                            SvcMapFile mapFile,
                                            CodeCompileUnit generatedCode,
                                            System.Collections.IEnumerable importErrors, 
                                            System.Collections.IEnumerable generatorErrors)
        { 
            // Check and report fatal error first... 
            HandleProxyGenerationErrors(importErrors);
            HandleProxyGenerationErrors(generatorErrors); 

            // if there is no fatal error, we expect valid type generated from the process
            //   unless there is no metadata files, or there is a service contract type sharing
            if (mapFile.MetadataList.Count > 0 && mapFile.ClientOptions.ServiceContractMappingList.Count == 0) 
            {
                if (!IsAnyTypeGenerated(generatedCode)) 
                { 
                    StringBuilder collectedMessages = new StringBuilder();
 
                    // merge error messages
                    CollectErrorMessages(importErrors, collectedMessages);
                    CollectErrorMessages(generatorErrors, collectedMessages);
 
                    throw new InvalidOperationException(String.Format(CultureInfo.CurrentCulture, WCFModelStrings.ReferenceGroup_FailedToGenerateCode, referenceDisplayName, collectedMessages.ToString()));
                } 
            } 
        }
 
        /// 
        /// Check whether we have generated any types
        /// 
        ///  
        /// 
        ///  
        private static bool IsAnyTypeGenerated(CodeCompileUnit compileUnit) 
        {
            if (compileUnit != null) 
            {
                foreach (System.CodeDom.CodeNamespace codeNamespace in compileUnit.Namespaces)
                {
                    if (codeNamespace.Types.Count > 0) 
                    {
                        return true; 
                    } 
                }
            } 
            return false;
        }

        ///  
        ///  Retrieve a VirtualDirectory for the given virtual path
        ///  
        ///  
        /// 
        private VirtualDirectory GetVirtualDirectory(string virtualPath) 
        {
            return HostingEnvironment.VirtualPathProvider.GetDirectory(VirtualPath);
        }
 
        /// 
        ///  Caculate our namespace for current VirtualPath... 
        ///  
        /// 
        ///  
        private string GetGeneratedNamespace()
        {
            // First, determine the namespace to use for code generation.  This is based on the
            //   relative path of the reference from its base App_WebReferences directory 

            // ... Get the virtual path to the App_WebReferences folder, e.g "/MyApp/App_WebReferences" 
            string rootWebRefDirVirtualPath = GetWebRefDirectoryVirtualPath(); 

            // ... Get the folder's directory path, e.g "/MyApp/Application_WebReferences/Foo/Bar", 
            //     where we'll look for .svcmap files
            string currentSubfolderUnderWebReferences = this.VirtualPath;
            if (currentSubfolderUnderWebReferences == null)
            { 
                Debug.Fail("Shouldn't be given a null virtual path");
                throw new InvalidOperationException(); 
            } 

            return CalculateGeneratedNamespace(rootWebRefDirVirtualPath, currentSubfolderUnderWebReferences); 
        }

        /// 
        /// Determine the namespace to use for the proxy generation 
        /// 
        /// The path to the App_WebReferences folder 
        /// The path to the current folder 
        /// 
        private static string CalculateGeneratedNamespace(string webReferencesRootVirtualPath, string virtualPath) 
        {
            // ... Ensure both folders have trailing slashes
            webReferencesRootVirtualPath = VirtualPathUtility.AppendTrailingSlash(webReferencesRootVirtualPath);
            virtualPath = VirtualPathUtility.AppendTrailingSlash(virtualPath); 

            Debug.Assert(virtualPath.StartsWith(webReferencesRootVirtualPath, StringComparison.OrdinalIgnoreCase), 
                "We expected to be inside the App_WebReferences folder"); 

            // ... Determine the namespace to use, based on the directory structure where the .svcmap file 
            //     is found.
            if (webReferencesRootVirtualPath.Length == virtualPath.Length)
            {
                Debug.Assert(string.Equals(webReferencesRootVirtualPath, virtualPath, StringComparison.OrdinalIgnoreCase), 
                    "We expected to be in the App_WebReferences directory");
 
                // If it's the root WebReferences dir, use the empty namespace 
                return String.Empty;
            } 
            else
            {
                // We're in a subdirectory of App_WebReferences.
                // Get the directory's relative path from App_WebReferences, e.g. "Foo/Bar" 

                virtualPath = VirtualPathUtility.RemoveTrailingSlash(virtualPath).Substring(webReferencesRootVirtualPath.Length); 
 
                // Split it into chunks separated by '/'
                string[] chunks = virtualPath.Split('/'); 

                // Turn all the relevant chunks into valid namespace chunks
                for (int i = 0; i < chunks.Length; i++)
                { 
                    chunks[i] = MakeValidTypeNameFromString(chunks[i]);
                } 
 
                // Put the relevant chunks back together to form the namespace
                return String.Join(".", chunks); 
            }
        }

        ///  
        /// Returns the app domain's application virtual path [from HttpRuntime.AppDomainAppVPath].
        /// Includes trailing slash, e.g. "/MyApp/" 
        ///  
        private static string GetAppDomainAppVirtualPath()
        { 
            string appVirtualPath = HttpRuntime.AppDomainAppVirtualPath;
            if (appVirtualPath == null)
            {
                Debug.Fail("Shouldn't get a null app virtual path from the app domain"); 
                throw new InvalidOperationException();
            } 
 
            return VirtualPathUtility.AppendTrailingSlash(VirtualPathUtility.ToAbsolute(appVirtualPath));
        } 

        /// 
        /// Gets the virtual path to the application's App_WebReferences directory, e.g. "/MyApp/App_WebReferences/"
        ///  
        private static string GetWebRefDirectoryVirtualPath()
        { 
            return VirtualPathUtility.Combine(GetAppDomainAppVirtualPath(), WebRefDirectoryName + @"\"); 
        }
 
        /// 
        /// Return a valid type name from a string by changing any character
        ///   that's not a letter or a digit to an '_'.
        ///  
        /// 
        ///  
        internal static string MakeValidTypeNameFromString(string typeName) 
        {
            if (String.IsNullOrEmpty(typeName)) 
                throw new ArgumentNullException("typeName");

            StringBuilder sb = new StringBuilder();
 
            for (int i = 0; i < typeName.Length; i++)
            { 
                // Make sure it doesn't start with a digit (ASURT 31134) 
                if (i == 0 && Char.IsDigit(typeName[0]))
                    sb.Append('_'); 

                if (Char.IsLetterOrDigit(typeName[i]))
                    sb.Append(typeName[i]);
                else 
                    sb.Append('_');
            } 
 
            // Identifier can't be a single underscore character
            string validTypeName = sb.ToString(); 
            if (validTypeName.Equals("_", StringComparison.Ordinal))
            {
                validTypeName = "__";
            } 

            return validTypeName; 
        } 

        ///  
        /// Get the appropriate tool configuration for this service reference.
        ///
        /// If a reference.config file is present, the configuration object returned
        /// will be the merged view of: 
        ///
        ///    Machine Config 
        ///       ReferenceConfig 
        ///
        /// If not reference.config file is present, the configuration object returned 
        /// will be a merged view of:
        ///
        ///     Machine.config
        ///         web.config in application's physical path... 
        ///
        ///  
        /// SvcMapFile representing the service 
        /// 
        private System.Configuration.Configuration GetToolConfig(SvcMapFile mapFile, string mapFilePath) 
        {
            string toolConfigFile = null;

            if (mapFile != null && mapFilePath != null) 
            {
                foreach (ExtensionFile extensionFile in mapFile.Extensions) 
                { 
                    if (String.Equals(extensionFile.Name, TOOL_CONFIG_ITEM_NAME, StringComparison.Ordinal))
                    { 
                        toolConfigFile = extensionFile.FileName;
                    }
                }
            } 

            System.Web.Configuration.WebConfigurationFileMap fileMap; 
            fileMap = new System.Web.Configuration.WebConfigurationFileMap(); 

            System.Web.Configuration.VirtualDirectoryMapping mapping; 
            if (toolConfigFile != null)
            {
                //
                // If we've got a specific tool configuration to use, we better load that... 
                //
                mapping = new System.Web.Configuration.VirtualDirectoryMapping(System.IO.Path.GetDirectoryName(mapFilePath), true, toolConfigFile); 
            } 
            else
            { 
                //
                // Otherwise we fall back to the default web.config file...
                //
                mapping = new System.Web.Configuration.VirtualDirectoryMapping(HostingEnvironment.ApplicationPhysicalPath, true); 
            }
            fileMap.VirtualDirectories.Add("/", mapping); 
 
            return System.Web.Configuration.WebConfigurationManager.OpenMappedWebConfiguration(fileMap, "/", System.Web.Hosting.HostingEnvironment.SiteName);
 
        }


        ///  
        /// Helper class to implement type resolution for the generator
        ///  
        private class TypeResolver : IContractGeneratorReferenceTypeLoader 
        {
            private System.Reflection.Assembly[] _referencedAssemblies; 

            private IEnumerable ReferencedAssemblies
            {
                get 
                {
                    if (_referencedAssemblies == null) 
                    { 
                        System.Collections.ICollection referencedAssemblyCollection = BuildManager.GetReferencedAssemblies();
                        _referencedAssemblies = new System.Reflection.Assembly[referencedAssemblyCollection.Count]; 
                        referencedAssemblyCollection.CopyTo(_referencedAssemblies, 0);
                    }
                    return _referencedAssemblies;
                } 
            }
 
            System.Type IContractGeneratorReferenceTypeLoader.LoadType(string typeName) 
            {
                // If the type can't be resolved, we need an exception thrown (thus the true argument) 
                //   so it can be reported as a build error.
                return BuildManager.GetType(typeName, true);
            }
 
            System.Reflection.Assembly IContractGeneratorReferenceTypeLoader.LoadAssembly(string assemblyName)
            { 
                System.Reflection.AssemblyName assemblyToLookFor = new System.Reflection.AssemblyName(assemblyName); 

                foreach (System.Reflection.Assembly assembly in ReferencedAssemblies) 
                {
                    if (System.Reflection.AssemblyName.ReferenceMatchesDefinition(assemblyToLookFor, assembly.GetName()))
                    {
                        return assembly; 
                    }
                } 
 
                throw new System.IO.FileNotFoundException(String.Format(CultureInfo.CurrentCulture, WCFModelStrings.ReferenceGroup_FailedToLoadAssembly, assemblyName));
            } 

            void IContractGeneratorReferenceTypeLoader.LoadAllAssemblies(out IEnumerable loadedAssemblies, out IEnumerable loadingErrors)
            {
                loadedAssemblies = ReferencedAssemblies; 
                loadingErrors = new System.Exception[] {};
            } 
 
        }
 
        private class ImportExtensionServiceProvider : IServiceProvider
        {

            #region IServiceProvider Members 

            public object GetService(Type serviceType) 
            { 
                // We don't currently provide any services to import extensions in the build provider context
                return null; 
            }

            #endregion
        } 

    } 
 
}
 

// File provided for Reference Use Only by Microsoft Corporation (c) 2007.

                        

Link Menu

Network programming in C#, Network Programming in VB.NET, Network Programming in .NET
This book is available now!
Buy at Amazon US or
Buy at Amazon UK