Beispiel #1
0
        /// <summary>
        /// Queries the workflow.
        /// </summary>
        /// <typeparam name="TQueryResult">The query result type.</typeparam>
        /// <param name="queryName">Identifies the query.</param>
        /// <param name="args">The query arguments.</param>
        /// <returns>The query result.</returns>
        /// <remarks>
        /// <note>
        /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters and
        /// result type passed are compatible with the target workflow query arguments.
        /// </note>
        /// </remarks>
        public async Task <TQueryResult> QueryAsync <TQueryResult>(string queryName, params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(queryName), nameof(queryName));
            Covenant.Requires <ArgumentNullException>(args != null, nameof(args));

            if (execution == null)
            {
                throw new InvalidOperationException("The stub must be started first.");
            }

            var reply = (WorkflowQueryReply)await client.CallProxyAsync(
                new WorkflowQueryRequest()
            {
                WorkflowId = execution.WorkflowId,
                RunId      = execution.RunId,
                Domain     = options.Domain,
                QueryName  = queryName,
                QueryArgs  = CadenceHelper.ArgsToBytes(client.DataConverter, args)
            });

            reply.ThrowOnError();

            return(client.DataConverter.FromData <TQueryResult>(reply.Result));
        }
Beispiel #2
0
        /// <summary>
        /// Signals the workflow.
        /// </summary>
        /// <param name="signalName">The signal name.</param>
        /// <param name="args">The signal arguments.</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        /// <exception cref="InvalidOperationException">Thrown if the child workflow has not been started.</exception>
        /// <remarks>
        /// <note>
        /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed
        /// are compatible with the target workflow signal arguments.
        /// </note>
        /// </remarks>
        public async Task SignalAsync(string signalName, params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName));
            Covenant.Requires <ArgumentNullException>(args != null, nameof(args));

            if (Execution == null)
            {
                throw new InvalidOperationException("The stub must be started first.");
            }

            var reply = await parentWorkflow.ExecuteNonParallel(
                async() =>
            {
                return((WorkflowSignalChildReply)await client.CallProxyAsync(
                           new WorkflowSignalChildRequest()
                {
                    ContextId = parentWorkflow.ContextId,
                    ChildId = childExecution.ChildId,
                    SignalName = signalName,
                    SignalArgs = CadenceHelper.ArgsToBytes(client.DataConverter, args)
                }));
            });

            reply.ThrowOnError();
        }
Beispiel #3
0
            public async Task SignalAsync(WorkflowExecution execution, string signalName, params object[] args)
            {
                await SyncContext.Clear;

                var dataConverter = Activity.Client.DataConverter;

                await Activity.Client.SignalWorkflowAsync(execution, signalName, CadenceHelper.ArgsToBytes(dataConverter, args));
            }
Beispiel #4
0
        /// <summary>
        /// Starts the target activity that returns <c>void</c>, passing the specified arguments.
        /// </summary>
        /// <param name="args">The arguments to be passed to the activity.</param>
        /// <returns>The <see cref="IAsyncFuture{T}"/> with the <see cref="IAsyncFuture{T}.GetAsync"/> that can be used to retrieve the workfow result.</returns>
        /// <exception cref="InvalidOperationException">Thrown when attempting to start a stub more than once.</exception>
        /// <remarks>
        /// <para>
        /// You must take care to pass parameters that are compatible with the target activity parameters.
        /// These are checked at runtime but not while compiling.
        /// </para>
        /// <note>
        /// Any given <see cref="ActivityFutureStub{TActivityInterface}"/> may only be executed once.
        /// </note>
        /// </remarks>
        public async Task <IAsyncFuture> StartAsync(params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow));
            parentWorkflow.SetStackTrace();

            if (hasStarted)
            {
                throw new InvalidOperationException("Cannot start a future stub more than once.");
            }

            var parameters = targetMethod.GetParameters();

            if (parameters.Length != args.Length)
            {
                throw new ArgumentException($"Invalid number of parameters: [{parameters.Length}] expected but [{args.Length}] were passed.", nameof(parameters));
            }

            hasStarted = true;

            // Cast the input parameters to the target types so that developers won't need to expicitly
            // cast things like integers into longs, floats into doubles, etc.

            for (int i = 0; i < args.Length; i++)
            {
                args[i] = CadenceHelper.ConvertArg(parameters[i].ParameterType, args[i]);
            }

            // Start the activity.

            var client              = parentWorkflow.Client;
            var dataConverter       = client.DataConverter;
            var activityConstructor = typeof(TActivityImplementation).GetConstructor(Type.EmptyTypes);
            var activityId          = parentWorkflow.GetNextActivityId();
            var activityActionId    = parentWorkflow.RegisterActivityAction(typeof(TActivityImplementation), activityConstructor, targetMethod);

            var reply = await parentWorkflow.ExecuteNonParallel(
                async() =>
            {
                return((ActivityStartLocalReply)await client.CallProxyAsync(
                           new ActivityStartLocalRequest()
                {
                    ContextId = parentWorkflow.ContextId,
                    ActivityId = activityId,
                    ActivityTypeId = activityActionId,
                    Args = CadenceHelper.ArgsToBytes(dataConverter, args),
                    Options = options.ToInternal()
                }));
            });

            reply.ThrowOnError();
            parentWorkflow.UpdateReplay(reply);

            // Create and return the future.

            return(new AsyncFuture(parentWorkflow, activityId));
        }
Beispiel #5
0
        /// <summary>
        /// Executes the associated workflow and waits for it to complete,
        /// returning the workflow result.
        /// </summary>
        /// <typeparam name="TResult">The workflow result type.</typeparam>
        /// <param name="args">The workflow arguments.</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        public async Task<TResult> ExecuteAsync<TResult>(params object[] args)
        {
            await SyncContext.Clear;
            Covenant.Requires<ArgumentNullException>(args != null, nameof(args));
            EnsureNotStarted();

            var argBytes = CadenceHelper.ArgsToBytes(client.DataConverter, args);

            Execution = await client.StartWorkflowAsync(WorkflowTypeName, argBytes, Options);

            return await GetResultAsync<TResult>();
        }
Beispiel #6
0
        /// <summary>
        /// Signals the associated workflow, starting it if it hasn't already been started.
        /// </summary>
        /// <param name="signalName">Specifies the signal name.</param>
        /// <param name="signalArgs">Specifies the signal arguments.</param>
        /// <param name="startArgs">Specifies the workflow start arguments.</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        public async Task<WorkflowExecution> SignalWithStartAsync(string signalName, object[] signalArgs, object[] startArgs)
        {
            await SyncContext.Clear;
            Covenant.Requires<ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName));
            Covenant.Requires<ArgumentNullException>(signalArgs != null, nameof(signalArgs));
            Covenant.Requires<ArgumentNullException>(startArgs != null, nameof(startArgs));

            var signalArgBytes = CadenceHelper.ArgsToBytes(client.DataConverter, signalArgs);
            var startArgBytes  = CadenceHelper.ArgsToBytes(client.DataConverter, startArgs);

            return await client.SignalWorkflowWithStartAsync(this.WorkflowTypeName, signalName, signalArgBytes, startArgBytes, this.Options);
        }
Beispiel #7
0
        /// <summary>
        /// Signals the associated workflow.
        /// </summary>
        /// <param name="signalName">Specifies the signal name.</param>
        /// <param name="args">Specifies the signal arguments.</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        public async Task SignalAsync(string signalName, params object[] args)
        {
            await SyncContext.Clear;
            Covenant.Requires<ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName));
            Covenant.Requires<ArgumentNullException>(args != null, nameof(args));
            EnsureStarted();

            if (Execution == null)
            {
                throw new InvalidOperationException("Signal cannot be sent because the stub doesn't have the workflow execution.");
            }

            var argBytes = CadenceHelper.ArgsToBytes(client.DataConverter, args);

            await client.SignalWorkflowAsync(Execution, signalName, argBytes, client.ResolveDomain(Options?.Domain));
        }
Beispiel #8
0
        /// <summary>
        ///  Queries the associated workflow specifying the expected result type as
        ///  a parameter.
        /// </summary>
        /// <param name="resultType">Specifies the query result type.</param>
        /// <param name="queryType">Specifies the query type.</param>
        /// <param name="args">Specifies the query arguments.</param>
        /// <returns>The query result as a <c>dynamic</c>.</returns>
        public async Task<object> QueryAsync(Type resultType, string queryType, params object[] args)
        {
            await SyncContext.Clear;
            Covenant.Requires<ArgumentNullException>(!string.IsNullOrEmpty(queryType), nameof(queryType));
            Covenant.Requires<ArgumentNullException>(args != null, nameof(args));
            EnsureStarted();

            if (Execution == null)
            {
                throw new InvalidOperationException("Query cannot be sent because the stub doesn't have the workflow execution.");
            }

            var argBytes = CadenceHelper.ArgsToBytes(client.DataConverter, args);

            return client.DataConverter.FromData(resultType, await client.QueryWorkflowAsync(Execution, queryType, argBytes, client.ResolveDomain(Options?.Domain)));
        }
Beispiel #9
0
        /// <summary>
        /// Starts the workflow, returning an <see cref="IAsyncFuture"/> that can be used
        /// to wait for the the workflow to complete and obtain its result.
        /// </summary>
        /// <typeparam name="TResult">The workflow result type.</typeparam>
        /// <param name="args">The workflow arguments.</param>
        /// <returns>An <see cref="ExternalWorkflowFuture{TResult}"/> that can be used to retrieve the workflow result as an <c>object</c>.</returns>
        /// <exception cref="InvalidOperationException">Thrown if the workflow has already been started.</exception>
        /// <remarks>
        /// <note>
        /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed
        /// and the result type are compatible with the target workflow method.
        /// </note>
        /// </remarks>
        public async Task <ExternalWorkflowFuture <TResult> > StartAsync <TResult>(params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(args != null, nameof(args));

            if (execution != null)
            {
                throw new InvalidOperationException("Cannot start a future stub more than once.");
            }

            execution = await client.StartWorkflowAsync(workflowTypeName, CadenceHelper.ArgsToBytes(client.DataConverter, args), options);

            // Create and return the future.

            return(new ExternalWorkflowFuture <TResult>(client, execution, options.Domain));
        }
Beispiel #10
0
        /// <summary>
        /// Signals the workflow.
        /// </summary>
        /// <param name="signalName">Specifies the signal name.</param>
        /// <param name="args">Specifies the signal arguments.</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        public async Task SignalAsync(string signalName, params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName));
            Covenant.Requires <ArgumentNullException>(args != null, nameof(args));

            if (parentWorkflow != null)
            {
                var stub = parentWorkflow.NewLocalActivityStub <ILocalOperations, LocalOperations>();

                await stub.SignalAsync(Execution, signalName, args);
            }
            else
            {
                await client.SignalWorkflowAsync(Execution, signalName, CadenceHelper.ArgsToBytes(client.DataConverter, args));
            }
        }
Beispiel #11
0
        /// <summary>
        /// <b>EXPERIMENTAL:</b> This method synchronously signals the workflow and returns
        /// only after the workflow has processed received and processed the signal as opposed
        /// to <see cref="SignalAsync"/> which is fire-and-forget and does not wait for the
        /// signal to be processed.
        /// </summary>
        /// <param name="signalName">
        /// The signal name as defined by the <see cref="SignalMethodAttribute"/>
        /// decorating the workflow signal method.
        /// </param>
        /// <param name="args">The signal arguments.</param>
        /// <remarks>
        /// <note>
        /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed
        /// are compatible with the target workflow arguments.  No compile-time type checking
        /// is performed for this method.
        /// </note>
        /// </remarks>
        public async Task SyncSignalAsync(string signalName, params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(signalName), nameof(signalName));
            Covenant.Requires <ArgumentNullException>(args != null, nameof(args));

            if (execution == null)
            {
                throw new InvalidOperationException("The stub must be started first.");
            }

            var signalId        = Guid.NewGuid().ToString("d");
            var argBytes        = CadenceHelper.ArgsToBytes(client.DataConverter, args);
            var signalCall      = new SyncSignalCall(signalName, signalId, argBytes);
            var signalCallBytes = CadenceHelper.ArgsToBytes(client.DataConverter, new object[] { signalCall });

            await client.SyncSignalWorkflowAsync(execution, signalName, signalId, signalCallBytes, options.Domain);
        }
Beispiel #12
0
        /// <summary>
        /// Starts the target activity that returns <c>void</c>, passing the specified arguments.
        /// </summary>
        /// <param name="args">The arguments to be passed to the activity.</param>
        /// <returns>The <see cref="IAsyncFuture{T}"/> with the <see cref="IAsyncFuture{T}.GetAsync"/> that can be used to retrieve the workfow result.</returns>
        /// <exception cref="InvalidOperationException">Thrown when attempting to start a future stub more than once.</exception>
        /// <remarks>
        /// <para>
        /// You must take care to pass parameters that are compatible with the target activity parameters.
        /// These are checked at runtime but not while compiling.
        /// </para>
        /// <note>
        /// Any given <see cref="ActivityFutureStub{TActivityInterface}"/> may only be executed once.
        /// </note>
        /// </remarks>
        public async Task <IAsyncFuture> StartAsync(params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow));
            parentWorkflow.SetStackTrace();

            if (hasStarted)
            {
                throw new InvalidOperationException("Cannot start a future stub more than once.");
            }

            hasStarted = true;

            // Start the activity.

            var client        = parentWorkflow.Client;
            var dataConverter = client.DataConverter;
            var activityId    = parentWorkflow.GetNextActivityId();

            var reply = await parentWorkflow.ExecuteNonParallel(
                async() =>
            {
                return((ActivityStartReply)await client.CallProxyAsync(
                           new ActivityStartRequest()
                {
                    ContextId = parentWorkflow.ContextId,
                    ActivityId = activityId,
                    Activity = activityTypeName,
                    Args = CadenceHelper.ArgsToBytes(dataConverter, args),
                    Options = options.ToInternal(),
                    Domain = options.Domain,
                }));
            });

            reply.ThrowOnError();
            parentWorkflow.UpdateReplay(reply);

            // Create and return the future.

            return(new AsyncFuture(parentWorkflow, activityId));
        }
Beispiel #13
0
        /// <summary>
        /// Starts the child workflow, returning an <see cref="IAsyncFuture"/> that can be used
        /// to wait for the workflow to complete.  This version doesn't return a workflow result.
        /// </summary>
        /// <param name="args">The workflow arguments.</param>
        /// <returns>An <see cref="ChildWorkflowFuture"/> that can be used to retrieve the workflow result as an <c>object</c>.</returns>
        /// <exception cref="InvalidOperationException">Thrown if the child workflow has already been started.</exception>
        /// <remarks>
        /// <note>
        /// <b>IMPORTANT:</b> You need to take care to ensure that the parameters passed
        /// are compatible with the target workflow arguments.
        /// </note>
        /// </remarks>
        public async Task <ChildWorkflowFuture> StartAsync(params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(args != null, nameof(args));

            if (childExecution != null)
            {
                throw new InvalidOperationException("Cannot start a future stub more than once.");
            }

            childExecution = await client.StartChildWorkflowAsync(parentWorkflow, WorkflowTypeName, CadenceHelper.ArgsToBytes(client.DataConverter, args), Options);

            // Create and return the future.

            return(new ChildWorkflowFuture(parentWorkflow, childExecution));
        }
Beispiel #14
0
        /// <summary>
        /// Starts the target activity that returns <typeparamref name="TActivityInterface"/>, passing the specified arguments.
        /// </summary>
        /// <typeparam name="TResult">The activity result type.</typeparam>
        /// <param name="args">The arguments to be passed to the activity.</param>
        /// <returns>The <see cref="IAsyncFuture{T}"/> with the <see cref="IAsyncFuture{T}.GetAsync"/> that can be used to retrieve the workfow result.</returns>
        /// <exception cref="InvalidOperationException">Thrown when attempting to start a future stub more than once.</exception>
        /// <remarks>
        /// <para>
        /// You must take care to pass parameters that are compatible with the target activity parameters.
        /// These are checked at runtime but not while compiling.
        /// </para>
        /// <note>
        /// Any given <see cref="ActivityFutureStub{TActivityInterface}"/> may only be executed once.
        /// </note>
        /// </remarks>
        public async Task <IAsyncFuture <TResult> > StartAsync <TResult>(params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow));
            parentWorkflow.SetStackTrace();

            if (hasStarted)
            {
                throw new InvalidOperationException("Cannot start a future stub more than once.");
            }

            var parameters = targetMethod.GetParameters();

            if (parameters.Length != args.Length)
            {
                throw new ArgumentException($"Invalid number of parameters: [{parameters.Length}] expected but [{args.Length}] were passed.", nameof(parameters));
            }

            hasStarted = true;

            // Cast the input parameters to the target types so that developers won't need to expicitly
            // cast things like integers into longs, floats into doubles, etc.

            for (int i = 0; i < args.Length; i++)
            {
                args[i] = CadenceHelper.ConvertArg(parameters[i].ParameterType, args[i]);
            }

            // Validate the return type.

            var resultType = targetMethod.ReturnType;

            if (resultType == typeof(Task))
            {
                throw new ArgumentException($"Activity method [{nameof(TActivityInterface)}.{targetMethod.Name}()] does not return [void].", nameof(TActivityInterface));
            }

            resultType = resultType.GenericTypeArguments.First();

            if (!resultType.IsAssignableFrom(typeof(TResult)))
            {
                throw new ArgumentException($"Activity method [{nameof(TActivityInterface)}.{targetMethod.Name}()] returns [{resultType.FullName}] which is not compatible with [{nameof(TResult)}].", nameof(TActivityInterface));
            }

            // Start the activity.

            var dataConverter = client.DataConverter;
            var activityId    = parentWorkflow.GetNextActivityId();

            var reply = await parentWorkflow.ExecuteNonParallel(
                async() =>
            {
                return((ActivityStartReply)await client.CallProxyAsync(
                           new ActivityStartRequest()
                {
                    ContextId = parentWorkflow.ContextId,
                    ActivityId = activityId,
                    Activity = activityTypeName,
                    Args = CadenceHelper.ArgsToBytes(dataConverter, args),
                    Options = options.ToInternal(),
                    Domain = options.Domain
                }));
            });

            reply.ThrowOnError();
            parentWorkflow.UpdateReplay(reply);

            // Create and return the future.

            return(new AsyncFuture <TResult>(parentWorkflow, activityId));
        }
Beispiel #15
0
        /// <summary>
        /// Starts the target workflow that returns <typeparamref name="TResult"/>, passing any specified arguments.
        /// </summary>
        /// <typeparam name="TResult">The workflow result type.</typeparam>
        /// <param name="args">The arguments to be passed to the workflow.</param>
        /// <returns>The <see cref="ChildWorkflowFuture{T}"/> with the <see cref="ChildWorkflowFuture{T}.GetAsync"/> that can be used to retrieve the workfow result.</returns>
        /// <exception cref="InvalidOperationException">Thrown when attempting to start a future stub more than once.</exception>
        /// <remarks>
        /// <para>
        /// You must take care to pass parameters that are compatible with the target workflow parameters.
        /// These are checked at runtime but not while compiling.
        /// </para>
        /// <note>
        /// Any given <see cref="ChildWorkflowStub{TWorkflowInterface}"/> may only be executed once.
        /// </note>
        /// </remarks>
        public async Task <ChildWorkflowFuture <TResult> > StartAsync <TResult>(params object[] args)
        {
            await SyncContext.Clear;

            Covenant.Requires <ArgumentNullException>(parentWorkflow != null, nameof(parentWorkflow));
            parentWorkflow.SetStackTrace();

            if (hasStarted)
            {
                throw new InvalidOperationException("Cannot start a future stub more than once.");
            }

            var parameters = targetMethod.GetParameters();

            if (parameters.Length != args.Length)
            {
                throw new ArgumentException($"Invalid number of parameters: [{parameters.Length}] expected but [{args.Length}] were passed.", nameof(parameters));
            }

            hasStarted = true;

            // Cast the input parameters to the target types so that developers won't need to expicitly
            // cast things like integers into longs, floats into doubles, etc.

            for (int i = 0; i < args.Length; i++)
            {
                args[i] = CadenceHelper.ConvertArg(parameters[i].ParameterType, args[i]);
            }

            // Start the child workflow and then construct and return the future.

            var client    = parentWorkflow.Client;
            var execution = await client.StartChildWorkflowAsync(parentWorkflow, workflowTypeName, CadenceHelper.ArgsToBytes(client.DataConverter, args), options);

            // Initialize the type-safe stub property such that developers can call
            // any query or signal methods.

            Stub = StubManager.NewChildWorkflowStub <TWorkflowInterface>(client, parentWorkflow, workflowTypeName, execution);

            // Create and return the future.

            var resultType = targetMethod.ReturnType;

            if (resultType == typeof(Task))
            {
                throw new ArgumentException($"Workflow method [{nameof(TWorkflowInterface)}.{targetMethod.Name}()] does not return [void].", nameof(TWorkflowInterface));
            }

            resultType = resultType.GenericTypeArguments.First();

            if (!resultType.IsAssignableFrom(typeof(TResult)))
            {
                throw new ArgumentException($"Workflow method [{nameof(TWorkflowInterface)}.{targetMethod.Name}()] returns [{resultType.FullName}] which is not compatible with [{nameof(TResult)}].", nameof(TWorkflowInterface));
            }

            return(new ChildWorkflowFuture <TResult>(parentWorkflow, execution));
        }