File: System\ServiceModel\Activities\Activation\WorkflowServiceHostFactory.cs
Project: ndp\cdf\src\WCF\System.ServiceModel.Activation\System.ServiceModel.Activation.csproj (System.ServiceModel.Activation)
//------------------------------------------------------------
// Copyright (c) Microsoft Corporation.  All rights reserved.
//------------------------------------------------------------
namespace System.ServiceModel.Activities.Activation
{
    using System.Activities;
    using System.Activities.Expressions;
    using System.Activities.XamlIntegration;
    using System.Collections.Generic;
    using System.Globalization;
    using System.IO;
    using System.Reflection;
    using System.Runtime;
    using System.Runtime.DurableInstancing;
    using System.Runtime.Serialization;
    using System.Security;
    using System.Security.Permissions;
    using System.ServiceModel.Activation;
    using System.Text;
    using System.Web;
    using System.Web.Compilation;
    using System.Web.Hosting;
    using System.Xaml;
    using System.Xml;
    using System.Xml.Linq;
 
    public class WorkflowServiceHostFactory : ServiceHostFactoryBase
    {
        const string SupportedVersionsGeneratedTypeNamePrefix = "SupportedVersionsGeneratedType_";
        const string SupportedVersionsFolder = "App_Code";
        string xamlVirtualFile;       
 
        public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
        {
            WorkflowServiceHost serviceHost = null;
 
            if (string.IsNullOrEmpty(constructorString))
            {
                throw FxTrace.Exception.AsError(new InvalidOperationException(SR.WorkflowServiceHostFactoryConstructorStringNotProvided));
            }
 
            if (baseAddresses == null)
            {
                throw FxTrace.Exception.ArgumentNull("baseAddresses");
            }
 
            if (baseAddresses.Length == 0)
            {
                throw FxTrace.Exception.AsError(new InvalidOperationException(SR.BaseAddressesNotProvided));
            }
 
            if (!HostingEnvironment.IsHosted)
            {
                throw FxTrace.Exception.AsError(new InvalidOperationException(SR.Hosting_ProcessNotExecutingUnderHostedContext("WorkflowServiceHostFactory.CreateServiceHost")));
            }
 
            // We expect most users will use .xamlx file instead of precompiled assembly 
            // Samples of xamlVirtualPath under all scenarios
            //                          constructorString  XamlFileBaseLocation    xamlVirtualPath
            // 1. Xamlx direct          ~/sub/a.xamlx      ~/sub/                  ~/sub/a.xamlx 
            // 2. CBA with precompiled  servicetypeinfo    ~/sub/servicetypeinfo   ~/sub/servicetypeinfo  * no file will be found
            // 3. CBA with xamlx        sub/a.xamlx        ~/                      ~/sub/a.xamlx
            // 4. Svc with precompiled  servicetypeinfo    ~/sub/servicetypeinfo   ~/sub/servicetypeinfo  * no file will be found
            // 5. Svc with Xamlx        ../a.xamlx         ~/sub/                  ~/a.xamlx
                          
            string xamlVirtualPath = VirtualPathUtility.Combine(AspNetEnvironment.Current.XamlFileBaseLocation, constructorString);
            Stream activityStream;
            string compiledCustomString;
            if (GetServiceFileStreamOrCompiledCustomString(xamlVirtualPath, baseAddresses, out activityStream, out compiledCustomString))
            {
                // 
 
                BuildManager.GetReferencedAssemblies();
                //XmlnsMappingHelper.EnsureMappingPassed();
 
                this.xamlVirtualFile = xamlVirtualPath;
               
                WorkflowService service;
                using (activityStream)
                {
                    string serviceName = VirtualPathUtility.GetFileName(xamlVirtualPath);
                    string serviceNamespace = String.Format(CultureInfo.InvariantCulture, "/{0}{1}", ServiceHostingEnvironment.SiteName, VirtualPathUtility.GetDirectory(ServiceHostingEnvironment.FullVirtualPath));
 
                    service = CreatetWorkflowServiceAndSetCompiledExpressionRoot(null, activityStream, XName.Get(XmlConvert.EncodeLocalName(serviceName), serviceNamespace));
                }
                
                if (service != null)
                {                   
                    serviceHost = CreateWorkflowServiceHost(service, baseAddresses);
                }
            }
            else
            {
                Type activityType = this.GetTypeFromAssembliesInCurrentDomain(constructorString);
                if (null == activityType)
                {
                    activityType = GetTypeFromCompileCustomString(compiledCustomString, constructorString);
                }
                if (null == activityType)
                {
                    //for file-less cases, try in referenced assemblies as CompileCustomString assemblies are empty.
                    BuildManager.GetReferencedAssemblies();
                    activityType = this.GetTypeFromAssembliesInCurrentDomain(constructorString);
                }
                if (null != activityType)
                {
                    if (!TypeHelper.AreTypesCompatible(activityType, typeof(Activity)))
                    {
                        throw FxTrace.Exception.AsError(new InvalidOperationException(SR.TypeNotActivity(activityType.FullName)));
                    }
 
                    Activity activity = (Activity)Activator.CreateInstance(activityType);
                    serviceHost = CreateWorkflowServiceHost(activity, baseAddresses);
                }
            }
            if (serviceHost == null)
            {
                throw FxTrace.Exception.AsError(
                    new InvalidOperationException(SR.CannotResolveConstructorStringToWorkflowType(constructorString)));
            }
 
            //The Description.Name and Description.NameSpace aren't included intentionally - because 
            //in farm scenarios the sole and unique identifier is the service deployment URL
            ((IDurableInstancingOptions)serviceHost.DurableInstancingOptions).SetScopeName(
                XName.Get(XmlConvert.EncodeLocalName(VirtualPathUtility.GetFileName(ServiceHostingEnvironment.FullVirtualPath)),
                String.Format(CultureInfo.InvariantCulture, "/{0}{1}", ServiceHostingEnvironment.SiteName, VirtualPathUtility.GetDirectory(ServiceHostingEnvironment.FullVirtualPath))));
 
            return serviceHost;
        }
 
        internal static object LoadXaml(Stream activityStream)
        {
            object serviceObject;
            XamlXmlReaderSettings xamlXmlReaderSettings = new XamlXmlReaderSettings();
            xamlXmlReaderSettings.ProvideLineInfo = true;
            XamlReader wrappedReader = ActivityXamlServices.CreateReader(new XamlXmlReader(XmlReader.Create(activityStream), xamlXmlReaderSettings));
            if (TD.XamlServicesLoadStartIsEnabled())
            {
                TD.XamlServicesLoadStart();
            }
            serviceObject = XamlServices.Load(wrappedReader);
            if (TD.XamlServicesLoadStopIsEnabled())
            {
                TD.XamlServicesLoadStop();
            }
            return serviceObject;
        }
        
        void AddSupportedVersions(WorkflowServiceHost workflowServiceHost, WorkflowService baseService)
        {
            AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
 
            IList<Tuple<string, Stream>> streams = null;
            string xamlFileName = Path.GetFileNameWithoutExtension(VirtualPathUtility.GetFileName(this.xamlVirtualFile));
            GetSupportedVersionStreams(xamlFileName, out streams);
           
            if (streams != null)
            {
                try
                {
                    foreach (Tuple<string, Stream> stream in streams)
                    {
                        try
                        {
                            WorkflowService service = CreatetWorkflowServiceAndSetCompiledExpressionRoot(stream.Item1, stream.Item2, baseService.Name);
                            if (service != null)
                            {
                                workflowServiceHost.SupportedVersions.Add(service);
                            }
                        }
                        catch (Exception e)
                        {
                            Exception newException;
                            if (Fx.IsFatal(e) || !TryWrapSupportedVersionException(stream.Item1, e, out newException))
                            {
                                throw;
                            }
 
                            throw FxTrace.Exception.AsError(newException);
                        }
                    }
                }
                finally
                {
                    foreach (Tuple<string, Stream> stream in streams)
                    {
                        stream.Item2.Dispose();
                    }
                }
            }
        }
 
        internal static bool TryWrapSupportedVersionException(string filePath, Exception e, out Exception newException)
        {
            // We replace the exception, rather than simply wrapping it, because the activation error page highlights 
            // the innermost exception, and we want that  exception to clearly show which file is the culprit.
            // Of course, if the exception has an inner exception, that will still get highlighted instead.
            // Also, for Xaml and XmlException, we don't propagate the line info, because the exception message already contains it.
            if (e is XmlException)
            {
                newException = new XmlException(SR.ExceptionLoadingSupportedVersion(filePath, e.Message), e.InnerException);
                return true;
            }
 
            if (e is XamlException)
            {
                newException = new XamlException(SR.ExceptionLoadingSupportedVersion(filePath, e.Message), e.InnerException);
                return true;
            }
 
            if (e is InvalidWorkflowException)
            {
                newException = new InvalidWorkflowException(SR.ExceptionLoadingSupportedVersion(filePath, e.Message), e.InnerException);
                return true;
            }
 
            if (e is ValidationException)
            {
                newException = new ValidationException(SR.ExceptionLoadingSupportedVersion(filePath, e.Message), e.InnerException);
                return true;
            }
 
            newException = null;
            return false;
        }
 
        internal static string GetSupportedVersionGeneratedTypeName(string filePath)
        {
            string activityName = Path.GetFileNameWithoutExtension(filePath).ToUpper(CultureInfo.InvariantCulture);
            
            StringBuilder sb = new StringBuilder(WorkflowServiceHostFactory.SupportedVersionsGeneratedTypeNamePrefix);
            for (int i = 0; i < activityName.Length; i++)
            {
                sb.Append(char.ConvertToUtf32(activityName, i));
            }
 
            return sb.ToString();
        }
 
        [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method HostingEnvironmentWrapper.UnsafeImpersonate().",
           Safe = "Demands unmanaged code permission, disposes impersonation context in a finally, and tightly scopes the usage of the impersonation token.")]
        [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
        [SecuritySafeCritical]
        internal static void GetSupportedVersionStreams(string xamlFileName, out IList<Tuple<string, Stream>> streams)
        {
            AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
 
            string virtualFileFolder = string.Format(CultureInfo.InvariantCulture, "~\\{0}", Path.Combine(SupportedVersionsFolder, xamlFileName));
            IDisposable unsafeImpersonate = null;
            List<Tuple<string, Stream>> streamList = new List<Tuple<string, Stream>>();
            bool cleanExit = false;
            streams = null;
            try
            {
                try
                {
                    try
                    {
                    }
                    finally
                    {
                        unsafeImpersonate = HostingEnvironmentWrapper.UnsafeImpersonate();
                    }
 
                    if (HostingEnvironment.VirtualPathProvider.DirectoryExists(virtualFileFolder))
                    {
                        string xamlFileFolder = HostingEnvironmentWrapper.MapPath(virtualFileFolder);
                        string[] files = Directory.GetFiles(xamlFileFolder, "*.xamlx");
                        foreach (string file in files)
                        {
                            Stream activityStream;
                            string path = Path.Combine(SupportedVersionsFolder, xamlFileName, Path.GetFileName(file));
                            string virtualFile = string.Format(CultureInfo.InvariantCulture, "~\\{0}", path);
 
                            if (HostingEnvironment.VirtualPathProvider.FileExists(virtualFile))
                            {
                                activityStream = HostingEnvironment.VirtualPathProvider.GetFile(virtualFile).Open();
                                streamList.Add(Tuple.Create(path, activityStream));
                            }
                        }
                    }
                    cleanExit = true;
                }
                finally
                {
                    if (unsafeImpersonate != null)
                    {
                        unsafeImpersonate.Dispose();
                        unsafeImpersonate = null;
                    }                    
                }
            }
            catch
            {
                cleanExit = false;
                throw;
            }
            finally
            {
                if (cleanExit)
                {
                    streams = streamList;
                }
                else
                {
                    foreach (Tuple<string, Stream> stream in streamList)
                    {
                        stream.Item2.Dispose();
                    }
                }
            }
        }
 
        static WorkflowService CreatetWorkflowServiceAndSetCompiledExpressionRoot(string supportedVersionXamlxfilePath, Stream activityStream, XName defaultServiceName)
        {
            WorkflowService service = CreatetWorkflowService(activityStream, defaultServiceName);
            if (service != null)
            {
                // CompiledExpression is not supported on Configuration Based Activation (CBA) scenario.
                if (ServiceHostingEnvironment.IsHosted && !ServiceHostingEnvironment.IsConfigurationBased)
                {
                    // We use ServiceHostingEnvironment.FullVirtualPath (instead of the constructor string) because we’re passing this path to BuildManager, 
                    // which may not understand the file type referenced by the constructor string (e.g. .xaml).
                    ICompiledExpressionRoot expressionRoot = XamlBuildProviderExtension.GetExpressionRoot(supportedVersionXamlxfilePath, service, ServiceHostingEnvironment.FullVirtualPath);
                    if (expressionRoot != null)
                    {
                        CompiledExpressionInvoker.SetCompiledExpressionRoot(service.Body, expressionRoot);
                    }
                }
            }
 
            return service;
        }
 
        internal static WorkflowService CreatetWorkflowService(Stream activityStream, XName defaultServiceName)
        {   
            WorkflowService service = null;
            object serviceObject;
 
            serviceObject = LoadXaml(activityStream);
   
            if (serviceObject is Activity)
            {
                service = new WorkflowService
                {
                    Body = (Activity)serviceObject
                };
            }
            else if (serviceObject is WorkflowService)
            {
                service = (WorkflowService)serviceObject;
            }
 
            // If name of the service is not set
            // service name = xaml file name with extension
            // service namespace = IIS virtual path
            // service config name = Activity.DisplayName
            if (service != null)
            {
                if (service.Name == null)
                {
                    service.Name = defaultServiceName;
 
                    if (service.ConfigurationName == null && service.Body != null)
                    {
                        service.ConfigurationName = XmlConvert.EncodeLocalName(service.Body.DisplayName);
                    }
                }
            }
            return service;
        }        
 
        Type GetTypeFromAssembliesInCurrentDomain(string typeString)
        {
            Type activityType = Type.GetType(typeString, false);
            if (null == activityType)
            {
                foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
                {
                    activityType = assembly.GetType(typeString, false);
                    if (null != activityType)
                    {
                        break;
                    }
                }
            }
            return activityType;
        }
 
        Type GetTypeFromCompileCustomString(string compileCustomString, string typeString)
        {
            if (string.IsNullOrEmpty(compileCustomString))
            {
                return null;
            }
            string[] components = compileCustomString.Split('|');
            if (components.Length < 3)
            {
                throw FxTrace.Exception.AsError(new InvalidOperationException(SR.InvalidCompiledString(compileCustomString)));
            }
            Type activityType = null;
            for (int i = 3; i < components.Length; i++)
            {
                Assembly assembly = Assembly.Load(components[i]);
                activityType = assembly.GetType(typeString, false);
                if (activityType != null)
                {
                    break;
                }
            }
            return activityType;
        }
 
        protected virtual WorkflowServiceHost CreateWorkflowServiceHost(Activity activity, Uri[] baseAddresses)
        {
            return new WorkflowServiceHost(activity, baseAddresses);
        }
 
        protected virtual WorkflowServiceHost CreateWorkflowServiceHost(WorkflowService service, Uri[] baseAddresses)
        {
            WorkflowServiceHost workflowServiceHost = new WorkflowServiceHost(service, baseAddresses);
            if (service.DefinitionIdentity != null)
            {
                AddSupportedVersions(workflowServiceHost, service);
            }
            return workflowServiceHost;
        }
 
        // The code is optimized for reducing impersonation 
        // true means serviceFileStream has been set; false means CompiledCustomString has been set
        [Fx.Tag.SecurityNote(Critical = "Calls SecurityCritical method HostingEnvironmentWrapper.UnsafeImpersonate().",
            Safe = "Demands unmanaged code permission, disposes impersonation context in a finally, and tightly scopes the usage of the impersonation token.")]
        [SecurityPermission(SecurityAction.Demand, UnmanagedCode = true)]
        [SecuritySafeCritical]
        bool GetServiceFileStreamOrCompiledCustomString(string virtualPath, Uri[] baseAddresses, out Stream serviceFileStream, out string compiledCustomString)
        {
            AspNetPartialTrustHelpers.FailIfInPartialTrustOutsideAspNet();
 
            IDisposable unsafeImpersonate = null;
            compiledCustomString = null;
            serviceFileStream = null;
            try
            {
                try
                {
                    try
                    {
                    }
                    finally
                    {
                        unsafeImpersonate = HostingEnvironmentWrapper.UnsafeImpersonate();
                    }
                    if (HostingEnvironment.VirtualPathProvider.FileExists(virtualPath))
                    {
                        serviceFileStream = HostingEnvironment.VirtualPathProvider.GetFile(virtualPath).Open();
                        return true;
                    }
                    else
                    {
                        if (!AspNetEnvironment.Current.IsConfigurationBased)
                        {
                            compiledCustomString = BuildManager.GetCompiledCustomString(baseAddresses[0].AbsolutePath);
                        }
                        return false;
                    }
                }
                finally
                {
                    if (null != unsafeImpersonate)
                    {
                        unsafeImpersonate.Dispose();
                    }
                }
            }
            catch
            {
                throw;
            }
        }
    }
}