locked
IIS (global.asax) and Durable Timer Polling (workflow services w/out endpoints) RRS feed

  • Question

  • We are hosting our workflow services (many) in IIS 6. The workflows basically perform state transitions. For these transitions, we rely on the content correlated (using unique id) WCF exposed endpoints to trigger the workflow transitions. Each transition is a pick with a delay and a receive activity. As a side note, we activate the workflow services using file-less activation via the serviceActivations entity in the web.config and the added workflow services specified in the config use a thin custom factory wrapper around WorkflowServiceHostFactory that allows us to configure extensions, behaviors and endpoints via code primary due to the fact that the DevArt Oracle persistence and tracking components don't support declarative configuration.

    The issue is with the durable timers related to the Delay activites. For this we heard that you use global.asax (application_start) to bring up all your workflow service hosts (without endpoints), each containing a workflow service, to handle the delay timer pollings. Sharepoint and AppFabric come with windows services that does this polling but with IIS this appears to be the only way to support these timers.  

    However, the problems (3) we face are on the host.Open() calls in global.asax, we get the exception, 1. “This collection already contains an address with scheme http. There can be at most one address per scheme in this collection. If your service is being hosted in IIS you can fix the problem by setting 'system.serviceModel/serviceHostingEnvironment/multipleSiteBindingsEnabled' to true”. This setting is set to true and these workflow service hosts appear to be conflicting with the endpoints dynamically activated (via serviceActivations) and waiting for IIS wcf requests on the workflow services. We changed the Uri supplied to the factory to be net.pipe (since we don't plan on using that protocol) for example and then we get the exception, 2. “HTTP could not register URL http://+:8732/. Your process does not have access rights to this namespace”. The suspected reason for the latter exception is security because we're not using a server level OS and simply needed to use netsh (e.g. netsh http add urlacl url=http://+:/ user=mylocaluser|domainuser) to grant privileges, however, it would be great if we could avoid this issue as well somehow and not require these permissions / reservations for each url / port / workflow.   The main problem we have is that 3. we can't seem to host the global.asax workflow services without endpoints, which seems reasonable as their purpose is only to poll the durable timers and wake up persisted instances. When we clear all the endpoints (host.Description.Endpoints.Clear()) or clear them and add a bogus one, the host open() here complains that the scheme and port from the serviceActivation instances are in effect and a conflict exists or complains a host can't have an endpoint or only infrastructure (e.g. control) end points.

    Does Microsoft have any guidance or have any examples of hosting workflow service hosts in global.asax for durable timers or insights into how to adjust the following approach? 

    We can't host using AppFabric and would prefer not to use a windows services to keep development and deployment hurdles low.

    The relevant code and configuration for this global.asax durable timer support is below.

    <protocolMapping>
      <add scheme="http" binding="wsHttpBinding" /> <!-- force default configuration for http protocols to use wshttp for security -->
    </protocolMapping>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" aspNetCompatibilityEnabled="true" >
      <serviceActivations>
        <add relativeAddress="~/WorkSpecV1.xamlx" service="WorkSpecV1.xamlx" factory="NMD.Workflow.NMDWorkflowServiceHostFactory" />
      </serviceActivations>
    </serviceHostingEnvironment
    <services>
      <service name="WorkSpecV1" behaviorConfiguration ="nmdWorkflowBehavior">
        <endpoint address="wce" binding="wsHttpBinding" kind="workflowControlEndpoint" />
        <endpoint contract="StateMachine" address="v1/WorkSpec" binding="wsHttpBinding" bindingConfiguration ="nmdWorkflowBinding" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8732/nmdwf/" />
          </baseAddresses>
        </host>
      </service>
    </services>


       protected void Application_Start(object sender, EventArgs e)
        {
            CreateServiceHost("WorkSpecV1.xamlx");
            foreach (System.ServiceModel.Activities.WorkflowServiceHost host in _hosts)
            {
                host.Open();
            }
        }

       private System.ServiceModel.Activities.WorkflowServiceHost CreateServiceHost(string xamlxName)
        {
            WorkflowService wfService = LoadService(xamlxName);
            System.ServiceModel.Activities.WorkflowServiceHost host = new NMD.Workflow.NMDWorkflowServiceHostFactory().
                NMDCreateWorkflowServiceHost(wfService, new Uri[] { new Uri("http://localhost:8732/nmdwf/") }, false);
            _hosts.Add(host);

            return host;
        }

        private WorkflowService LoadService(string xamlxName)
        {
            String rootAppPath = HttpRuntime.AppDomainAppPath + "Workflows";
            String fullFilePath = System.IO.Path.Combine(rootAppPath, xamlxName);
            WorkflowService service = XamlServices.Load(fullFilePath) as WorkflowService;
            if (service != null)
            {
                return service;
            }
            else
            {
                throw new NullReferenceException(String.Format("Unable to load workflow service definition from {0}", fullFilePath));
            }
        }

     

    public class NMDWorkflowServiceHostFactory : System.ServiceModel.Activities.Activation.WorkflowServiceHostFactory {

        private bool _constructEndPoints;

    protected override System.ServiceModel.Activities.WorkflowServiceHost CreateWorkflowServiceHost(WorkflowService service, Uri[] baseAddresses)
        {
            System.ServiceModel.Activities.WorkflowServiceHost workflowServiceHost = base.CreateWorkflowServiceHost(service, baseAddresses);

            WorkflowRuntimeBehavior runtimeBehavior = workflowServiceHost.Description.Behaviors.Find<WorkflowRuntimeBehavior>();
            if (runtimeBehavior != null) CreateRunTimeEventHandlers(runtimeBehavior);
            AddBehaviorsAndEndpoints(workflowServiceHost);

            return workflowServiceHost;
        }

    public System.ServiceModel.Activities.WorkflowServiceHost NMDCreateWorkflowServiceHost(WorkflowService service, Uri[] baseAddresses, bool constructEndPoints)
        {
            _constructEndPoints = constructEndPoints;
            return this.CreateWorkflowServiceHost(service, baseAddresses);
        }

       private void AddBehaviorsAndEndpoints(System.ServiceModel.Activities.WorkflowServiceHost host, bool constructEndPoints = true)
        {
            // Add endpoints for any services that have been defined in the workflow
            if (constructEndPoints) host.AddDefaultEndpoints();

            WSHttpBinding binding = new WSHttpBinding();
            if (constructEndPoints) { EndpointAddress endpointAddress = new EndpointAddress(host.BaseAddresses[0]); }

            host.WorkflowExtensions.Add<ConfigurationExtension>(() => new ConfigurationExtension()); // singleton

            host.Description.Behaviors.Add(new OracleInstanceStoreBehavior
            {
                ConnectionString = "omitted cx string;",
                InstanceEncodingOption = InstanceEncodingOption.GZip,
                InstanceCompletionAction = InstanceCompletionAction.DeleteAll,
                InstanceLockedExceptionAction = InstanceLockedExceptionAction.BasicRetry,
                HostLockRenewalPeriod = new TimeSpan(00, 00, 30),
                RunnableInstancesDetectionPeriod = new TimeSpan(00, 00, 05)
            });

            System.ServiceModel.Activities.Description.WorkflowIdleBehavior idleBehavior = new System.ServiceModel.Activities.Description.WorkflowIdleBehavior();
            idleBehavior.TimeToPersist = new TimeSpan(0, 0, 0);
            idleBehavior.TimeToUnload = new TimeSpan(0, 0, 0);
            host.Description.Behaviors.Remove<System.ServiceModel.Activities.Description.WorkflowIdleBehavior>();
            host.Description.Behaviors.Add(idleBehavior);

            OracleTrackingParticipant oracleTracking = new OracleTrackingParticipant("omitted cx strng;");
            System.Activities.Tracking.TrackingProfile profile = new System.Activities.Tracking.TrackingProfile();
            profile.Name = "nmdTrackingAll";
            oracleTracking.TrackingProfile = profile;
            host.WorkflowExtensions.Add(oracleTracking);
        }

    Friday, January 13, 2012 7:54 PM