Closed AmrTealeb closed 4 months ago
Can you share the full stack trace of the exception?
/cc @sfmskywalker
There probably won’t be a stack trace if the workflow manager catches and swallows any exceptions, which I believe it does. Instead of rethrowing it sets the workflow’s status to Faulted.
@amrtealeb is there any chance you could share your source code with me? If not I’ll try and reproduce over the weekend and see what’s going on.
Actually your code above should suffice.
I do wonder what the type of Input.Content
is, as well as its value?
I looked into it, and I have a couple of observations and questions.
I tried setting up the exact same workflow as depicted above, however I failed to find the activity displayed as "Validate Content" where I could set a minimum and maximum. Is this a custom activity?
I continued setting up the workflow without the "Validate Content" activity and copied your code into a controller, then executed that controller. The workflow completed successfully with no faults. Therefore I assume there's something not right with the "Validate Content" activity.
Anyway, I'm curious as to why you'd be manually invoking a workflow that starts with the HttpRequestEvent
. Such workflows will already be invoked for you. And better yet - each and every blocking activity will have an appropriate support class that will cause halted workflows to be resumed.
Your approach of automatically resuming halted workflows immediately will defeat the purpose of having workflows that can be edited by a dashboard user, because now your code will have to understand each type of blocking activity of when it should be resumed and with what stimuli.
That being said, if you have short-lived workflows (workflows that don't contain any blocking activities), you can take the approach you depicted above. All you'd need to do then is remove the part where you check for remaining (blocking) activities, since there won't be any. You can then also just remove the HttpRequestEvent
as the starting activity, and instead mark the "Set Article" activity as the start activity.
Your workflow will now behave as a regular function that receives input and returns output (accessible via the returned workflow execution context).
But if you do want to manually invoke a specific workflow that is potentially long-running, I recommend that all you do is start the workflow (using _workflowManager.StartWorkflowAsync
), and not automatically resume the workflow, as it won't make sense. Nor would you have to do so: as soon as there's a halted workflow instance, it will be resumed when the appropriate event & stimuli is received.
I hope this helps, but let me know if you have any questions or doubts.
Is it possible to have the workflow defined in code? Because it seems to me at the moment that it can only be configured via the designer which serialize the workflow in json.
Thank you for your reply
Validate Content is a custom activity
I tried using _workflowManager.StartWorkflowAsync for a HttpRequestEvent and it works fine but i have some questions here
When i run await _workflowManager.StartWorkflowAsync(workflowType, startActivity);
inside a controller method how does my payload get passed to the StartWorkflowAsyync method?
@dodyg You can certainly define a workflow in code. You'd instantiate a new WorkflowType
, set its Activities
and Transitions
properties, and persist it. For example, the following code defines a workflow type consisting of an HTTP Request Event, an HTTP Response Action, a transition connecting the two, and persisting it so it can be instantiated:
var activity1 = new ActivityRecord
{
ActivityId = Guid.NewGuid().ToString("N"),
Name = nameof(HttpRequestEvent),
IsStart = true,
Properties = JObject.FromObject(new
{
Url = "http://foo",
Method = "GET"
})
};
var activity2 = new ActivityRecord
{
ActivityId = Guid.NewGuid().ToString("N"),
Name = nameof(HttpResponseTask),
Properties = JObject.FromObject(new
{
HttpStatusCode = 200,
Content = new WorkflowExpression<string>("Hello World!")
})
};
var transition = new Transition
{
Id = 1,
SourceActivityId = activity1.ActivityId,
DestinationActivityId = activity2.ActivityId,
SourceOutcomeName = "Done"
};
var workflowType = new WorkflowType
{
Activities = new [] { activity1, activity2 },
Transitions = new [] { transition }
};
await _workflowTypeStore.SaveAsync(workflowType);
@AmrTealeb
There's a 3rd argument that takes a dictionary representing your payload (called input
). Here's the full method signature:
Task<WorkflowExecutionContext> StartWorkflowAsync(WorkflowType workflowType, ActivityRecord startActivity = null, IDictionary<string, object> input = null, string correlationId = null)
@sfmskywalker OK great. Let me try this out and add some helper functions to type the properties creation. JObject.FromObject
is super flexible but prone to typos.
@dodyg I gave your question some more thought, and realized that I might have missed an important aspect to your question: would it be possible to define a workflow in code and have it participate in the workflow triggering system, without having to persist it? The short answer to that would be 'no', because your workflow defined in code wouldn't be available to the workflow manager otherwise.
However I've been researching other open source workflow libraries, and came across a project called Workflow Core, which enables one to define a workflow in code and have it executed. Unfortunately, it doesn't have a designer, so you would be stuck to defining all of your workflows in code (which might be sufficient in many scenarios, not certainly not all).
I've been wanting for a long time now to take the best of some of the worlds out there:
To achieve all that, I'm working on Elsa Workflows that basically consists of two projects: a standalone HTML5 web based designer, and a .NET Standard workflow library that allows you to define workflows in code like Workflow Core and load & execute workflow definitions & instances defined as YAML.
I'm almost at the point where I can start working on a gallery module for Orchard as a proof of concept. I don't know if there would be any interest in taking it in, or if it would be even feasible, but I want to try.
I am investigating of the suitability of using the Orchard Core workflow in a business application. If the persistence is a requirement, it should not a deal breaker as long as we find a way to override the persistence mechanism to other storage other than YesSql.
I think one of the main appeal of a workflow system is its accessibility to expert users. Having a workflow without a web designer really limits its applicability.
I discovered Esla Workflow yesterday and filed a question(https://github.com/elsa-workflows/elsa-core/issues/47). I think yeah integrating Esla to be part of Orchard Core ecosystem is going to be important because the Orchard Core ecosystem will be big once it reaches some level of maturity. There is no other such ecosystem exists in .NET Core world.
If I'm going to include Workflows in a business application (not CMS). Should I go with Elsa-Workflows or OrchardCore.Workflows?
I did a new implementation for WorkflowTypeStore to save data in SQL server, how can i register my new class to be used for IWorkflowTypeStore interface?
I tried this in my Startupcs file but didn't work
services.AddScoped<IWorkflowTypeStore, WorkflowTypeSqlStore>();
Try to add in your module manifest a dependency on the workflow feature so that your startup will run after the workflow one, this way i think you will be able to really override the default implementation.
If I'm going to include Workflows in a business application (not CMS). Should I go with Elsa-Workflows or OrchardCore.Workflows?
You can use Orchard Core Framework for business application just fine. @AmrTealeb is trying out OrchardCore.Workflows in business application. I am investigating esla-workflow. I suspect you can go either way but trying it out in a test project is the only way to find out.
@jtkech Thank you, i did this and it works. Is there a way i make the CMS use my implementation for IWorkflowTypeStore and IWorkflowStore ?
@Kinani I recommend going with OrchardCore.Workflows for now, as Elsa is not ready yet for prime time. After that however, things might be different.
@Kinani I recommend going with OrchardCore.Workflows for now, as Elsa is not ready yet for prime time. After that however, things might be different.
Okay, thank you all for your great contributions.
I tried to include OrchardCore.Workflows as advised, so far what i did:
.AddOrchardCore().AddMvc()
in ConfigureServices
app.UseOrchardCore();
in Configure
I get the following Exception
Unable to resolve service for type `YesSql.ISession' while attempting to activate 'OrchardCore.Workflows.Http.Services.HttpRequestRouteActivator'.
Stack trace: at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateEnumerable(Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.CreateServiceAccessor(Type serviceType) at System.Collections.Concurrent.ConcurrentDictionary
2.GetOrAdd(TKey key, Func
2 valueFactory) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider) at OrchardCore.Modules.ModularTenantContainerMiddleware.Invoke(HttpContext httpContext) in C:\Users\Kinani\Source\OrchardCore\src\OrchardCore\OrchardCore\Modules\ModularTenantContainerMiddleware.cs:line 76
It might need a storage abstraction so that yessql would only be a custom implementation.
@sfmskywalker
I was trying to run this workflow
using this code
await _workflowManager.StartWorkflowAsync(workflowType, activities.First(), input);
but the status is Faulted and the error message is "Error Specified argument was out of the range of valid values. Parameter name: column Actual value was 1."
Do you know what can be the cause for this?
@sfmskywalker Also i have another question about the workflow in previous comment When i run it the status is always either Finished or Faulted and never Idle or Halted Shouldn't the Signal Event halt the workflow until the signal is provided?
@AmrTealeb A workflow will be halted when execution enters an event activity (such as Signal) only if it's not the start of the workflow. If an event is the start of a workflow, the workflow will execute when the event is triggered. The reason it works this way is to allow an event on a workflow to actually cause that workflow to run when the event is triggered, and to halt the workflow when an event is encountered later on. When that happens, workflow execution resumes once the event is triggered that is blocking the workflow instance.
So that's why your workflow executes immediately: it starts with a Signal activity (an event), so invoking StartWorkflowAsync
will cause your workflow to start.
However, you should not have to invoke the workflow yourself at all - the whole point of having "event" activities is so that the workflows will execute automatically when the appropriate event is triggered. For example, when you add a Signal event to your workflow, that workflow will automatically start as soon as that signal is triggered. The class that is responsible for this will internally invoke TriggerEventAsync
, which in turn will call StartWorkflowAsync
and ResumeWorkflowAsync
to start new workflows and resume existing workflows, respectively.
If you are manually invoking workflows yourself using StartWorkflowAsync
, then there's no point to start your workflow with a Signal (Input Event in your example) - you could just have it start with Set Booking Property
. But if you do want to execute your workflow in response to a Signal, then your workflow is fine and you should not do this: await _workflowManager.StartWorkflowAsync(workflowType, activities.First(), input);
. Instead, your application should trigger the Signal event by making an HTTP request to the HttpWorkflowController's Trigger action.
Alternatively, if you prefer to trigger a workflow yourself from your own controller that is invoked from your application, then you could come up with your own event activity and use its name when invoking _workflowManager.TriggerEventAsync()
. However, you might just as well use the existing HttpWorkflowController and use it to first generate a URL so that you can encode the signal (to prevent tampering), and use that URL to trigger the workflow over HTTP.
To summarize:
SignalEvent
, HttpRequestEvent
and ContentCreatedEvent
activities have been implemented. More instructively, look at HttpWorkflowController
and ContentsHandler. Hope this helps, but let me know if anything is unclear.
@sfmskywalker
Thank you for your help
I removed the HttpRequestEvent
, now when i run the workflow it is Halted
as expected
but when i trigger the event using
await _workflowManager.ResumeWorkflowAsync(workflow, activity, input);
The workflow is Faulted
with error message
Unable to cast object of type 'OrchardCore.DisplayManagement.Liquid.LiquidViewTemplate' to type 'Esprima.Ast.Program'.
Is there a way i make the CMS use my implementation for IWorkflowTypeStore and IWorkflowStore ?
Yes, you should be able to register your own implementation using e.g. services.Replace(ServiceDescriptor.Scoped(typeof(IWorkflowTypeStore), typeof(MyCustomWorkflowTypeStore))
(I haven't checked this in detail, this is just to give you an idea) and making sure your feature depends on the workflows feature so that your Startup
runs after the Workflow's one. This works the same way for any other Orchard service you wish to provide a custom implementation for.
You shouldn't even have to "replace" it. Just register another implementation and the DI will use the latest one. As long as your feature depends on the Workflow one.
That's true. Just out of curiosity, when would one use the Replace extension method? I've seen it used in the OpenId EF module.
This is useful when we inject an IEnumerable<ISomething>
or use .GetServices<ISomething>()
and we don't want that the one we override will get collected.
That makes sense. Thanks!
Is there a way i can use orchard workflow designer as a stand alone feature? in other words, include the designer in my project without the CMS?
You should be able to do so , but you would still depend on the Orchard Core Framework. The Workflows module depends on Orchard's drivers & shape systems for example.
Yes, we are planning to use Orchard Core Framework with a business application project
We are trying to wire up the workflows in our project First i want to choose a workflow type, instantiate it and pass some values to it So i created this workflow using the designer
I noticed the WorkflowManager class so i decided to use it
Then i write some code to:
`
`
but then workflow.Status is Faulted and workflow.FaultMessage is "Error Specified argument was out of the range of valid values. Parameter name: column Actual value was 1."