public MethodInfo Resolve(IServiceDefinition serviceDefinition, RoutineMethodId methodId)
        {
#warning Should use Service interface type to get the interface mapping to resolve the proper method?
#warning Must use versioning to redirect to a different method.

            var serviceType = serviceDefinition.Implementation;
            if (serviceType == null)
            {
                throw new InvalidOperationException($"Impossible to resolve a method implementation on the external service '{serviceDefinition.Name}'.");
            }

            var methods = serviceType.GetMethods(
                BindingFlags.Public |
                BindingFlags.NonPublic |
                BindingFlags.Instance |
                BindingFlags.Static |
#warning if not found in declared on this type, go to base type
#warning also neede to check implemented interfaces if the input type is an interface
                BindingFlags.DeclaredOnly);

            var method = methods.SingleOrDefault(mi => mi.Name == methodId.MethodName);

            if (method == null)
            {
                throw new MissingMethodException($"The service '{serviceDefinition.Name}' does not have a method '{methodId.MethodName}'.");
            }

            return(method);
        }
        public async void NotifyCompletion(
            ServiceId serviceId,
            RoutineMethodId methodId,
            string intentId,
            TaskCompletionSource <TaskResult> completionSink,
            CancellationToken ct)
        {
            FabricConnectorAndRoutineInfo fabricConnectorAndRoutineInfo;

            while (true)
            {
                lock (_committedRoutines)
                {
                    if (_committedRoutines.TryGetValue(intentId, out fabricConnectorAndRoutineInfo))
                    {
                        _committedRoutines.Remove(intentId);
                        break;
                    }
                }
                await Task.Delay(5);
            }

#warning Add infrastructure exception handling

            for (var i = 0; fabricConnectorAndRoutineInfo.RoutineInfo.Result == null; i++)
            {
#warning Ideally need to handle 'fire an forget' cases - the continuation is never set?
                //if (!proxyTask.continuation == null after 1 min?)
                //    return;

                fabricConnectorAndRoutineInfo.RoutineInfo = await fabricConnectorAndRoutineInfo.FabricConnector.PollRoutineResultAsync(fabricConnectorAndRoutineInfo.RoutineInfo, ct);

                if (fabricConnectorAndRoutineInfo.RoutineInfo.Result != null)
                {
                    break;
                }

                TimeSpan delayInterval;

                if (fabricConnectorAndRoutineInfo.RoutineInfo is IRoutinePollInterval routinePollInterval)
                {
                    delayInterval = routinePollInterval.Suggest(i);
                }
                else
                {
                    delayInterval = TimeSpan.FromSeconds(0.5);
                }

                await Task.Delay(delayInterval);
            }

            lock (_routineResults)
                _routineResults.Add(intentId, fabricConnectorAndRoutineInfo.RoutineInfo.Result);

            Task.Run(() =>
                     completionSink.SetResult(fabricConnectorAndRoutineInfo.RoutineInfo.Result)
                     );
        }
예제 #3
0
        private async Task HandleResultPoll(HttpContext context, string serviceName, string methodName, string intentId)
        {
            var rfc7240Preferences = GetPreferences(context.Request.Headers);

            var serviceId = new ServiceId
            {
                ServiceName = serviceName
            };

            var methodId = new RoutineMethodId
            {
                MethodName = methodName
            };

            TaskResult taskResult = null;

            var waitTime = rfc7240Preferences.Wait;

            if (waitTime > MaxLongPollTime)
            {
                waitTime = MaxLongPollTime;
            }

            if (waitTime <= TimeSpan.Zero)
            {
                taskResult = await _routineCompletionNotifier.TryPollCompletionAsync(serviceId, methodId, intentId, default);
            }
            else
            {
                var cts = new CancellationTokenSource();
                try
                {
                    var completionSink = new TaskCompletionSource <TaskResult>();
                    _routineCompletionNotifier.NotifyCompletion(serviceId, methodId, intentId, completionSink, cts.Token);
                    taskResult = await completionSink.Task.WithTimeout(waitTime);
                }
                catch (TaskCanceledException)
                {
                    cts.Cancel();
                }
            }

            if (taskResult == null)
            {
                context.Response.Headers.Add("Date", DateTimeOffset.Now.ToString("u"));
                context.Response.StatusCode = 304; // Not Modified
            }
            else
            {
                var contentType         = context.Request.GetContentType();
                var isDasyncJsonRequest = string.Equals(contentType.MediaType, "application/dasync+json", StringComparison.OrdinalIgnoreCase);
                await RespondWithRoutineResult(context, taskResult, isDasyncJsonRequest);
            }
        }
 public Task <TaskResult> TryPollCompletionAsync(
     ServiceId serviceId,
     RoutineMethodId methodId,
     string intentId,
     CancellationToken ct)
 {
     lock (_routineResults)
     {
         _routineResults.TryGetValue(intentId, out var taskResult);
         return(Task.FromResult(taskResult));
     }
 }
예제 #5
0
        public MethodInfo Resolve(Type serviceType, RoutineMethodId methodId)
        {
#warning Should use Service interface type to get the interface mapping to resolve the proper method?
#warning Must use versioning to redirect to a different method.

            var methods = serviceType.GetMethods(
                BindingFlags.Public |
                BindingFlags.NonPublic |
                BindingFlags.Instance |
                BindingFlags.Static |
#warning if not found in declared on this type, go to base type
#warning also neede to check implemented interfaces if the input type is an interface
                BindingFlags.DeclaredOnly);

            var method = methods.SingleOrDefault(mi => mi.Name == methodId.MethodName);

            if (method == null)
            {
                throw new Exception($"method '{methodId.MethodName}' not found on type '{serviceType.FullName}'");
            }

            return(method);
        }
        public void NotifyCompletion(
            ServiceId serviceId,
            RoutineMethodId methodId,
            string intentId,
            TaskCompletionSource <TaskResult> completionSink,
            CancellationToken ct)
        {
            var taskResult = TryPollCompletionAsync(serviceId, methodId, intentId, default).Result;

            if (taskResult != null)
            {
                completionSink.SetResult(taskResult);
                return;
            }

            lock (_sinks)
            {
                if (!_sinks.TryGetValue(intentId, out var set))
                {
                    _sinks.Add(intentId, set = new List <TaskCompletionSource <TaskResult> >());
                }
                set.Add(completionSink);
            }
        }
예제 #7
0
        public async Task <HttpResponseMessage> ProcessRequestAsync(
            HttpRequestMessage request,
            FunctionExecutionContext context,
            DateTimeOffset requestStartTime,
            ILogger logger,
            CancellationToken ct)
        {
            _currentFunction.Value = new ExecutingFuncionInfo
            {
                FunctionName = context.FunctionName
            };

            try
            {
#warning Create an HTTP connector
                if (request.Method == HttpMethod.Post)
                {
                    string   serviceName = null;
                    string   routineName = null;
                    long?    intentId    = null;
                    bool     @volatile   = false;
                    TimeSpan pollTime    = TimeSpan.Zero;

                    foreach (var parameter in request.GetQueryNameValuePairs())
                    {
                        switch (parameter.Key)
                        {
                        case "service":
                            serviceName = parameter.Value;
                            break;

                        case "routine":
                            routineName = parameter.Value;
                            break;

                        case "intent":
                            if (long.TryParse(parameter.Value, out var parsedIntentId))
                            {
                                intentId = parsedIntentId;
                            }
                            break;
                        }
                    }

                    foreach (var header in request.Headers)
                    {
                        var firstValue = header.Value.FirstOrDefault();

                        switch (header.Key)
                        {
                        case "Volatile":
                            if (string.IsNullOrEmpty(firstValue) ||
                                !bool.TryParse(firstValue, out @volatile))
                            {
                                @volatile = true;
                            }
                            break;

                        case "Poll-Time":
                            if (!string.IsNullOrEmpty(firstValue))
                            {
                                if (double.TryParse(firstValue, out var pollTimeInSeconds))
                                {
                                    pollTime = TimeSpan.FromSeconds(pollTimeInSeconds);
                                }
                                else if (TimeSpan.TryParse(firstValue, out var pollTimeTimeSpan))
                                {
                                    pollTime = pollTimeTimeSpan;
                                }
                            }
                            break;
                        }
                    }

                    var serviceId = new ServiceId
                    {
                        ServiceName = serviceName
                    };

                    var routineMethodId = new RoutineMethodId
                    {
                        MethodName = routineName
                    };

                    var registration = _serviceRegistry.AllRegistrations
                                       .SingleOrDefault(r => r.ServiceName == serviceId.ServiceName);

                    if (registration == null || registration.IsExternal)
                    {
                        return new HttpResponseMessage(HttpStatusCode.BadRequest)
                               {
                                   Content = new StringContent(
                                       registration == null
                                ? "Service not found"
                                : "Cannot invoke external service")
                               }
                    }
                    ;

                    IValueContainer parameterContainer = null;
                    if (request.Content != null)
                    {
                        var routineMethod = _routineMethodResolver.Resolve(registration.ServiceType, routineMethodId);

                        var methodInvoker = _methodInvokerFactory.Create(routineMethod);
                        parameterContainer = methodInvoker.CreateParametersContainer();

#warning change to use a serializer based on the format
                        var paramsJson = await request.Content.ReadAsStringAsync();

                        if (!string.IsNullOrWhiteSpace(paramsJson))
                        {
                            JsonConvert.PopulateObject(paramsJson, parameterContainer);
                        }
                    }

                    var intent = new ExecuteRoutineIntent
                    {
#warning Generate intent ID from function request ID? any benefit (like on re-try)?
                        Id           = intentId ?? _idGenerator.NewId(),
                        ServiceId    = serviceId,
                        MethodId     = routineMethodId,
                        Parameters   = parameterContainer,
                        Continuation = null
                    };

                    var connector   = GetConnector(serviceId);
                    var routineInfo = await connector.ScheduleRoutineAsync(intent, ct);

                    if (pollTime > TimeSpan.Zero)
                    {
                        routineInfo = await PollRoutineAsync(routineInfo, connector, requestStartTime, pollTime, ct);
                    }

                    if (routineInfo.Result != null)
                    {
                        return(CreateResponseFromRoutineResult(routineInfo.Result));
                    }

                    var response = new HttpResponseMessage(HttpStatusCode.Accepted);

                    var location = $"{request.RequestUri.AbsolutePath}?service={serviceId.ServiceName}&routineId={routineInfo.RoutineId}";
                    response.Headers.Add("Location", location);

#warning ETag must be in certain format
                    //if (!string.IsNullOrEmpty(routineInfo.ETag))
                    //    response.Headers.ETag = new EntityTagHeaderValue(routineInfo.ETag);

                    return(response);
                }
                else if (request.Method == HttpMethod.Get)
                {
                    string   serviceName = null;
                    string   routineId   = null;
                    string   routineETag = null;
                    TimeSpan pollTime    = TimeSpan.Zero;

                    foreach (var parameter in request.GetQueryNameValuePairs())
                    {
                        switch (parameter.Key)
                        {
                        case "service":
                            serviceName = parameter.Value;
                            break;

                        case "routineId":
                            routineId = parameter.Value;
                            break;

                        case "routineTag":
                            routineETag = parameter.Value;
                            break;
                        }
                    }

                    foreach (var header in request.Headers)
                    {
                        var firstValue = header.Value.FirstOrDefault();

                        switch (header.Key)
                        {
                        case "Poll-Time":
                            if (!string.IsNullOrEmpty(firstValue))
                            {
                                if (double.TryParse(firstValue, out var pollTimeInSeconds))
                                {
                                    pollTime = TimeSpan.FromSeconds(pollTimeInSeconds);
                                }
                                else if (TimeSpan.TryParse(firstValue, out var pollTimeTimeSpan))
                                {
                                    pollTime = pollTimeTimeSpan;
                                }
                            }
                            break;
                        }
                    }

                    var serviceId = new ServiceId
                    {
                        ServiceName = serviceName
                    };

                    var routineInfo = new ActiveRoutineInfo
                    {
                        RoutineId = routineId,
                        ETag      = routineETag
                    };

                    var connector = GetConnector(serviceId);

                    routineInfo = await PollRoutineAsync(routineInfo, connector, requestStartTime, pollTime, ct);

                    if (routineInfo.Result != null)
                    {
                        return(CreateResponseFromRoutineResult(routineInfo.Result));
                    }

                    var response = new HttpResponseMessage(HttpStatusCode.NoContent);

#warning ETag must be in certain format
                    //if (!string.IsNullOrEmpty(routineInfo.ETag))
                    //    response.Headers.ETag = new EntityTagHeaderValue(routineInfo.ETag);

                    return(response);
                }
            }
            catch (Exception ex)
            {
                return(CreateInfrastructureErrorResponse(ex));
            }
            finally
            {
                _currentFunction.Value = default;
            }

            return(new HttpResponseMessage(HttpStatusCode.NotFound));
        }
예제 #8
0
        private async Task HandleEventReactionAsync(IServiceDefinition serviceDefinitionFilter, HttpContext context, CancellationToken ct)
        {
            if (context.Request.Method != "PUT" && context.Request.Method != "POST")
            {
                context.Response.StatusCode  = 405; // Method Not Allowed
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("To invoke a reaction to an event, use one of these HTTP verbs: GET, POST, PUT"));

                return;
            }

            var eventName = (context.Request.Query.TryGetValue("event", out var eventValues) && eventValues.Count == 1) ? eventValues[0] : null;

            if (string.IsNullOrWhiteSpace(eventName))
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("Missing 'event=xxx' in the URL query."));

                return;
            }

            var serviceName = (context.Request.Query.TryGetValue("service", out var serviceValues) && serviceValues.Count == 1) ? serviceValues[0] : null;

            if (string.IsNullOrWhiteSpace(serviceName))
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("Missing 'service=xxx' in the URL query."));

                return;
            }

            var eventDesc = new EventDescriptor
            {
                EventId = new EventId {
                    EventName = eventName
                },
                ServiceId = new ServiceId {
                    ServiceName = serviceName
                }
            };

            var eventHandlers = _eventDispatcher.GetEventHandlers(eventDesc);

            if ((eventHandlers?.Count ?? 0) == 0)
            {
                context.Response.StatusCode = DasyncHttpCodes.Succeeded;
                return;
            }

            var contentType         = context.Request.GetContentType();
            var isJsonRequest       = string.Equals(contentType.MediaType, "application/json", StringComparison.OrdinalIgnoreCase);
            var isDasyncJsonRequest = string.Equals(contentType.MediaType, "application/dasync+json", StringComparison.OrdinalIgnoreCase);

            if (!isJsonRequest && !isDasyncJsonRequest)
            {
                context.Response.StatusCode  = 406; // Not Acceptable
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The request content type is either not specified or not supported"));

                return;
            }

            string parametersJson = null;

            if (isJsonRequest)
            {
                parametersJson = await context.Request.ReadBodyAsStringAsync(contentType);
            }
            else if (isDasyncJsonRequest)
            {
                var envelopeJson = await context.Request.ReadBodyAsStringAsync(contentType);

                var envelope = JsonConvert.DeserializeObject <EventEnvelope>(envelopeJson, JsonSettings);
                parametersJson = envelope.Parameters;
            }

            var results = new List <RaiseEventResult>();

            _transitionUserContext.Current = GetUserContext(context);

            foreach (var subscriber in eventHandlers)
            {
                if (serviceDefinitionFilter != null && !string.Equals(subscriber.ServiceId.ServiceName, serviceDefinitionFilter.Name, StringComparison.OrdinalIgnoreCase))
                {
                    continue;
                }

                var subscriberServiceDefinition = _communicationModelProvider.Model.FindServiceByName(subscriber.ServiceId.ServiceName);

#warning Use model for method resolution instead
                var eventHandlerRoutineMethodId = new RoutineMethodId
                {
                    MethodName = subscriber.MethodId.MethodName
                };
                MethodInfo routineMethod = _routineMethodResolver.Resolve(subscriberServiceDefinition, eventHandlerRoutineMethodId);

                var methodInvoker      = _methodInvokerFactory.Create(routineMethod);
                var parameterContainer = methodInvoker.CreateParametersContainer();

                if (!string.IsNullOrWhiteSpace(parametersJson))
                {
                    if (isDasyncJsonRequest)
                    {
                        _dasyncJsonSerializer.Populate(parametersJson, parameterContainer);
                    }
                    else
                    {
                        JsonConvert.PopulateObject(parametersJson, parameterContainer, JsonSettings);
                    }
                }

                var intentId = _idGenerator.NewId();

                _intentPreprocessor.PrepareContext(context);
                if (await _intentPreprocessor.PreprocessAsync(context, subscriberServiceDefinition, subscriber.MethodId, parameterContainer).ConfigureAwait(false))
                {
                    return;
                }

                foreach (var postAction in _transitionActions)
                {
                    await postAction.OnRoutineStartAsync(subscriberServiceDefinition, subscriber.ServiceId, subscriber.MethodId, intentId);
                }

                var  serviceInstance = _domainServiceProvider.GetService(subscriberServiceDefinition.Implementation);
                Task task;
                try
                {
                    task = methodInvoker.Invoke(serviceInstance, parameterContainer);
                    if (routineMethod.ReturnType != typeof(void))
                    {
                        try { await task; } catch { }
                    }
                }
                catch (Exception ex)
                {
                    if (ex is TargetInvocationException)
                    {
                        ex = ex.InnerException;
                    }
                    task = Task.FromException(ex);
                }
                var taskResult = task?.ToTaskResult() ?? new TaskResult();

                foreach (var postAction in _transitionActions)
                {
                    await postAction.OnRoutineCompleteAsync(subscriberServiceDefinition, subscriber.ServiceId, subscriber.MethodId, intentId, taskResult);
                }

                results.Add(new RaiseEventResult
                {
                    ServiceName = subscriber.ServiceId.ServiceName,
                    MethodName  = subscriber.MethodId.MethodName,
                    Result      = taskResult
                });
            }

            context.Response.StatusCode = DasyncHttpCodes.Succeeded;

            if (isJsonRequest)
            {
                context.Response.ContentType = "application/json";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(results, JsonSettings)));
            }
            else if (isDasyncJsonRequest)
            {
                context.Response.ContentType = "application/dasync+json";
                _dasyncJsonSerializer.Serialize(context.Response.Body, results);
            }
        }
예제 #9
0
        public async Task HandleAsync(PathString basePath, HttpContext context, CancellationToken ct)
        {
            var headers = context.Response.Headers;

            headers.Add(DasyncHttpHeaders.PoweredBy, "D-ASYNC");

            var basePathSegmentCount = basePath.Value.Split('/', StringSplitOptions.RemoveEmptyEntries).Length;
            var pathSegments         = context.Request.Path.Value.Split('/', StringSplitOptions.RemoveEmptyEntries).Skip(basePathSegmentCount).ToArray();

            if (pathSegments.Length == 0 && context.Request.Query.ContainsKey("react"))
            {
                await HandleEventReactionAsync(null, context, ct);

                return;
            }

            if (pathSegments.Length == 0)
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("Empty request URL"));

                return;
            }

            var serviceName       = pathSegments[0];
            var serviceDefinition = _communicationModelProvider.Model.FindServiceByName(serviceName);

            if (serviceDefinition == null)
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"Service '{serviceName}' is not registered"));

                return;
            }

            if (serviceDefinition.Type == ServiceType.External)
            {
                context.Response.StatusCode  = 403; // Forbidden
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"Cannot invoke external service '{serviceName}' on your behalf"));

                return;
            }

            if (_eventDispatcher != null && pathSegments.Length == 1 && context.Request.Query.ContainsKey("react"))
            {
                await HandleEventReactionAsync(serviceDefinition, context, ct);

                return;
            }

            if (pathSegments.Length == 1)
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The request URL does not contain a service method"));

                return;
            }

            if (_eventDispatcher != null && context.Request.Query.ContainsKey("subscribe"))
            {
                await HandleEventSubsciptionAsync(serviceDefinition, pathSegments[1], context, ct);

                return;
            }

            var methodName = pathSegments[1];

            if (pathSegments.Length == 3)
            {
                await HandleResultPoll(context, serviceName, methodName, intentId : pathSegments[2]);

                return;
            }

            if (pathSegments.Length > 3)
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The request URL contains extra segments"));

                return;
            }

            var isQueryRequest      = context.Request.Method == "GET";
            var isCommandRequest    = context.Request.Method == "PUT" || context.Request.Method == "POST";
            var contentType         = context.Request.GetContentType();
            var isJsonRequest       = string.Equals(contentType.MediaType, "application/json", StringComparison.OrdinalIgnoreCase);
            var isDasyncJsonRequest = string.Equals(contentType.MediaType, "application/dasync+json", StringComparison.OrdinalIgnoreCase);

            if (!isQueryRequest && !isJsonRequest && !isDasyncJsonRequest)
            {
                context.Response.StatusCode  = 406; // Not Acceptable
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The request content type is either not specified or not supported"));

                return;
            }

            if (!isQueryRequest && !isCommandRequest)
            {
                context.Response.StatusCode  = 405; // Method Not Allowed
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("The service method invocation must use one of these HTTP verbs: GET, POST, PUT"));

                return;
            }

            var routineMethodId = new RoutineMethodId
            {
                MethodName = methodName
            };

#warning Use model for method resolution instead
            MethodInfo routineMethod;
            try
            {
                routineMethod = _routineMethodResolver.Resolve(serviceDefinition, routineMethodId);
            }
            catch
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"The service '{serviceDefinition.Name}' does not have method '{methodName}'"));

                return;
            }

#warning Use model to determine if the method is command or query
            var isQueryMethod = GetWordAndSynonyms.Contains(GetFirstWord(methodName));

            if (isQueryRequest && !isQueryMethod)
            {
                context.Response.StatusCode  = 404;
                context.Response.ContentType = "text/plain";
                await context.Response.Body.WriteAsync(Encoding.UTF8.GetBytes($"The method '{methodName}' of service '{serviceDefinition.Name}' is a command, but not a query, thus must be invoked with POST or PUT verb"));

                return;
            }

            string parametersJson = null;
            ContinuationDescriptor continuationDescriptor = null;

            if (isQueryRequest)
            {
                var inputObject = new JObject();

                foreach (var kvPair in context.Request.Query)
                {
                    if (kvPair.Value.Count == 0)
                    {
                        continue;
                    }

                    var parameterName = kvPair.Key;

                    if (kvPair.Value.Count == 1)
                    {
                        var parameterValue = kvPair.Value[0];
                        inputObject.Add(parameterName, parameterValue);
                    }
                    else
                    {
                        var values = new JArray();
                        foreach (var value in kvPair.Value)
                        {
                            values.Add(value);
                        }
                        inputObject.Add(parameterName, values);
                    }
                }

                parametersJson = inputObject.ToString();
            }
            else if (isJsonRequest)
            {
                parametersJson = await context.Request.ReadBodyAsStringAsync(contentType);
            }
            else
            {
                var inputJson = await context.Request.ReadBodyAsStringAsync(contentType);

                var envelope = JsonConvert.DeserializeObject <CommandEnvelope>(inputJson, JsonSettings);
                parametersJson         = envelope.Parameters;
                continuationDescriptor = envelope.Continuation;
            }

            var methodInvoker      = _methodInvokerFactory.Create(routineMethod);
            var parameterContainer = methodInvoker.CreateParametersContainer();

            if (!string.IsNullOrWhiteSpace(parametersJson))
            {
                if (isDasyncJsonRequest)
                {
                    _dasyncJsonSerializer.Populate(parametersJson, parameterContainer);
                }
                else
                {
                    JsonConvert.PopulateObject(parametersJson, parameterContainer, JsonSettings);
                }
            }

            var externalRequestId     = context.Request.Headers.TryGetValue(DasyncHttpHeaders.RequestId, out var requestIdValues) && requestIdValues.Count > 0 ? requestIdValues[0] : null;
            var externalCorrelationId = context.Request.Headers.TryGetValue(DasyncHttpHeaders.CorrelationId, out var correlationIdValues) && correlationIdValues.Count > 0 ? correlationIdValues[0] : null;

            var rfc7240Preferences = GetPreferences(context.Request.Headers);

            var isHttpRequestBlockingExecution = !(rfc7240Preferences.RespondAsync == true);

            _transitionUserContext.Current = GetUserContext(context);

            _intentPreprocessor.PrepareContext(context);
            if (await _intentPreprocessor.PreprocessAsync(context, serviceDefinition, routineMethodId, parameterContainer).ConfigureAwait(false))
            {
                return;
            }

            var serviceId = new ServiceId {
                ServiceName = serviceDefinition.Name
            };
            var intentId = _idGenerator.NewId();

            if (isQueryMethod)
            {
                var serviceInstance = _domainServiceProvider.GetService(serviceDefinition.Implementation);

                foreach (var postAction in _transitionActions)
                {
                    await postAction.OnRoutineStartAsync(serviceDefinition, serviceId, routineMethodId, intentId);
                }

                Task task;
                try
                {
                    task = methodInvoker.Invoke(serviceInstance, parameterContainer);
                    if (routineMethod.ReturnType != typeof(void))
                    {
                        try { await task; } catch { }
                    }
                }
                catch (Exception ex)
                {
                    if (ex is TargetInvocationException)
                    {
                        ex = ex.InnerException;
                    }
                    task = Task.FromException(ex);
                }
                var taskResult = task?.ToTaskResult() ?? new TaskResult();

                foreach (var postAction in _transitionActions)
                {
                    await postAction.OnRoutineCompleteAsync(serviceDefinition, serviceId, routineMethodId, intentId, taskResult);
                }

                await RespondWithRoutineResult(context, taskResult, isDasyncJsonRequest);
            }
            else
            {
                var intent = new ExecuteRoutineIntent
                {
                    Id        = intentId,
                    ServiceId = new ServiceId {
                        ServiceName = serviceDefinition.Name
                    },
                    MethodId     = routineMethodId,
                    Parameters   = parameterContainer,
                    Continuation = continuationDescriptor
                };

                var actions = new ScheduledActions
                {
                    ExecuteRoutineIntents = new List <ExecuteRoutineIntent> {
                        intent
                    }
                };

                var options = new TransitionCommitOptions
                {
                    NotifyOnRoutineCompletion = isHttpRequestBlockingExecution,
                    RequestId     = externalRequestId,
                    CorrelationId = externalCorrelationId ?? externalRequestId
                };

                await _transitionCommitter.CommitAsync(actions, null, options, default);

                var waitTime = rfc7240Preferences.Wait;
                if (isHttpRequestBlockingExecution || waitTime > MaxLongPollTime)
                {
                    waitTime = MaxLongPollTime;
                }

                if (waitTime > TimeSpan.Zero)
                {
                    var cts            = new CancellationTokenSource();
                    var completionSink = new TaskCompletionSource <TaskResult>();
                    _routineCompletionNotifier.NotifyCompletion(intent.ServiceId, intent.MethodId, intent.Id, completionSink, cts.Token);
                    try
                    {
                        var taskResult = await completionSink.Task.WithTimeout(waitTime);
                        await RespondWithRoutineResult(context, taskResult, isDasyncJsonRequest);

                        return;
                    }
                    catch (TaskCanceledException)
                    {
                        cts.Cancel();
                    }
                }

                var location = context.Request.Path.ToString() + "/" + intent.Id;
                context.Response.Headers.Add("Location", location);
                context.Response.StatusCode = DasyncHttpCodes.Scheduled;
                return;
            }
        }