/// <remarks> /// Fire and forget mode (async void). /// </remarks> public async void ExecuteAndAwaitInBackground(ExecuteRoutineIntent intent, Task proxyTask) { // Tell platform to track the completion. // Do it before commit as a routine may complete immediately. var tcs = new TaskCompletionSource <TaskResult>(); _routineCompletionNotifier.NotifyCompletion(intent.ServiceId, intent.MethodId, intent.Id, tcs, default); // Commit intent var options = new TransitionCommitOptions { // Set the hint about the synchronous call mode. NotifyOnRoutineCompletion = true }; var actions = new ScheduledActions { ExecuteRoutineIntents = new List <ExecuteRoutineIntent> { intent } }; await _transitionCommitter.CommitAsync(actions, transitionCarrier : null, options : options, ct : default); // Await for completion and set the result. var routineResult = await tcs.Task; proxyTask.TrySetResult(routineResult); }
/// <remarks> /// Fire and forget mode (async void). /// </remarks> public async void ExecuteAndAwaitInBackground(ExecuteRoutineIntent intent, Task proxyTask) { // Commit intent // Set the hint about the synchronous call mode. intent.NotifyOnCompletion = true; var actions = new ScheduledActions { ExecuteRoutineIntents = new List <ExecuteRoutineIntent> { intent } }; await _transitionCommitter.CommitAsync(actions, transitionCarrier : null, ct : default(CancellationToken)); // Tell platform to track the completion. var tcs = new TaskCompletionSource <TaskResult>(); _routineCompletionNotifier.NotifyCompletion(intent.Id, tcs); // Await for completion and set the result. var routineResult = await tcs.Task; proxyTask.TrySetResult(routineResult); }
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; } }