With Elsa Workflow 2.0 how to create an activity based on HttpEndpoint - elsa-workflows

We're trying to use Elsa for a project, but we're facing some difficulties now, so need suggestions badly. One thing we're trying to do is to create an Activity based on existing HttpEndpoint. However, with the source code got from https://github.com/elsa-workflows/elsa-core, after googled some docs and samples, we haven't been able to figure it out.
Here is what exactly we're attempting to do.
create a new Activity based on HttpEndpoint
make the Path include WorkflowInstanceId by default
a little bit more customizations needed in our scenario
Looking forward to suggestions and guidance. Thanks!

You could do something like what the Webhooks module is doing. Instead of
inheriting from HttpEndpoint, it uses an IActivityTypeProvider implementation that dynamically yields new activity types that reuse HttpEndpoint.
In your case, your activity type provider would only have to yield a single activity type (e.g. MyEndpoint) that pre-configures any and all aspects that you want, including the default Path property value.
Deriving a new activity type from HttpEndpoint directly works too. You will have to implement your own bookmark provider that provides HttpEndpointBookmark objects - the HTTP middleware for the HttpEndpoint activity relies on that.
Example:
public class MyEndpointBookmarkProvider : BookmarkProvider<HttpEndpointBookmark>
{
public override bool SupportsActivity(BookmarkProviderContext context) => context.ActivityType.TypeName == nameof(MyEndpoint);
public override async ValueTask<IEnumerable<BookmarkResult>> GetBookmarksAsync(BookmarkProviderContext context, CancellationToken cancellationToken)
{
var path = await context.ReadActivityPropertyAsync<MyEndpoint, PathString>(x => x.Path, cancellationToken);
var methods = (await context.ReadActivityPropertyAsync<MyEndpoint, HashSet<string>>(x => x.Methods, cancellationToken))?.Select(ToLower) ?? Enumerable.Empty<string>();
BookmarkResult CreateBookmark(string method) => Result(new(path, method), nameof(HttpEndpoint));
return methods.Select(CreateBookmark);
}
private static string ToLower(string s) => s.ToLowerInvariant();
}
The above bookmark provider provides bookmarks for your custom activity when the activity being indexed is of type "MyEndpoint".
Alternatively, you might go a different route altogether and simply implement an API endpoint (an ASP.NET Core controller, middleware or route endpoint) that triggers workflows based on your custom activity. I've written some documentation about that process here: https://elsa-workflows.github.io/elsa-core/docs/next/guides/guides-blocking-activities

Related

How to add a Dialog in a bot using SDK4.0

I am trying to implement a bot which uses Qna services and Azure search.
I am taking help of the C# QnA Maker sample github code.
It is using a BotServices.cs class which is taking a QnA service in its constructor. This Botservice object is being passed to the QnABot class constructor.
I want to use Dialog set in QnABot's constructor which need accessors to be added. I really didn't understand how to add accessor class and use them in the startup.cs
I tried to copy some code from other samples but didn't work.
Please help me to add an accessor to the BotServices constructor so that I can use dialog sets inside of it.
I would like to extend the QnA sample for my purpose.
Can you tell us why you want to pass a dialog set tot the botservices class? this class is only used to reference external services such as QnAMaker and LUIS. If you want to start a Dialog, do so in the OnTurnAsync method of the QnABot.cs class. keep in mind that the this method as it is created in this specific sample will send a response on every message the user sends even if they are working through a dialog. You could change the OnTurnAsync in such a way that the first step in the dialog is to check the QnAMaker. See the enterpriseBot sample to see how to start a dialog as well as adding an accessor to a child Dialog. see the following snipped from the MainDialog.cs class how they added the accessor:
protected override async Task OnStartAsync(DialogContext innerDc, CancellationToken cancellationToken = default(CancellationToken))
{
var onboardingAccessor = _userState.CreateProperty<OnboardingState>(nameof(OnboardingState));
var onboardingState = await onboardingAccessor.GetAsync(innerDc.Context, () => new OnboardingState());
var view = new MainResponses();
await view.ReplyWith(innerDc.Context, MainResponses.Intro);
if (string.IsNullOrEmpty(onboardingState.Name))
{
// This is the first time the user is interacting with the bot, so gather onboarding information.
await innerDc.BeginDialogAsync(nameof(OnboardingDialog));
}
}

new RoleManager<IdentityRole> error missing arguments in VS 2015

I want to implement User and Role Manager in VS 2015 using the Identity.EntityFramework": "3.0.0-rc1-final".
Among others I have created a class IdentityManager.
My main problem is creating a method to check the existence of a Role as follows.
public bool RoleExists(string name)
{
var RoleManager = new RoleManager<IdentityRole>(new RoleStore<IdentityRole>(new ApplicationDbContext()));
return RoleManager.RoleExists(name);
}
I keep getting the error on new RoleManager<IdentityRole>:
There is no argument given that corresponds to roleValidators, keyNormalizer, errors, logger,contextAccessor"
Yes, basically all the parameters I am not specifying but I have no idea how to approach these.
I am very new at this and have been searching and trying for days now, if someone can just point me in the right direction I am willing to do the legwork and testing, I just need some documentation.
I am having a similar issue - it looks like the roles are not the best option in identity 3.0
This thread (ASP .NET 5 MVC 6 Identity 3 Roles Claims Groups) helped me get something working, but its sad that this is not better documented.
Here are my attempts at improving that. Asp.net.Identity (3.0.0.0-rc1-final)
in Startup.cs --> ConfigurationServices
//Define your policies here, they are strings associated with claims types, that have claim strings...
//they need to be in AspNetUserClaims table, user id, department, Dev to be allowed access to the Dev policy
//add the auth option, below that makes it work, and in the api controller, add the
//[Authorize("Dev")] attribute
//services.AddAuthorization(
// options =>
// {
// options.AddPolicy("Dev", policy => { policy.RequireClaim("department", "Dev"); });
// });

How do I do cross-entity server-side validation

In my application, I have cross-entity validation logic that requires me to look at the entire change set and I'm doing this using the BeforeSaveEntities override.
I can construct the right logic by examining the saveMap parameter, but what am I supposed to do if I find something invalid?
If I throw an exception, like I would for single entity validation in the BeforeSaveEntity override, the whole save is aborted and the error is reported to the client. But some of the entities might be valid so I would want to save those and only abort the invalid parts.
Because BeforeSaveEntities returns a saveMap, I think I should be able to remove the invalid entities from the change set and continue to save the valid entities, but then how do I report the invalid parts to the client?
Is it possible to do a partial save of only the valid entities and at the same time, report a sensible error to the client to describe the parts of the save that failed?
Jay told you the way it is.
I wouldn't hold my breath waiting for Breeze to change because I think yours is a rare scenario and it isn't one we would want to encourage anyway.
But I'm weird and I can't stop thinking what I'd do if were you and I absolutely HAD to do it. I might try something like this.
Warning: this is pseudo-code and I'm making this up. I do not recommend or warrant this
Create a custom MyCustomEFContextProvider that derives from EFContextProvider.
Give it an ErrorEntities property to hold the error object
Override (shadow) the SaveChanges method with another that delegates to the base
public new CustomSaveResult SaveChanges(JObject saveBundle,
TransactionSettings transactionSettings = null) {
var result = base.SaveChanges(saveBundle, transactionSettings);
// learn about CustomSaveResult below
return new CustomSaveResult(this.ErrorEntities, result);
}
Catch an invalid entity inside BeforeSaveEntities
Pass it with error message to your custom ErrorEntities property
You get to that property via the EntityInfo instance as in
((MyCustomEFContextProvider) info.ContextProvider).ErrorEntities.Add(new ErrorEntity(info, message));
Remove the invalid entity from the SaveMap so it won't be included in the actual save
Let the save continue
The second line of your override SaveChanges method creates a new instance of your CustomSaveResult from the standard one and returns that to the caller.
public class CustomSaveResult : SaveResult {
public List ErrorEntities;
public CustomSaveResult(List errorEntities, SaveResult result){
// copy over everything
this.Entities = result.Entities;
this.KeyMappings = result.KeyMappings;
this.Errors = this.Errors;
// and now your error stuff
this.ErrorEntities = errorEntities;
}
}
Let's assume the caller is your Web API controller's SaveChanges method. Well you don't have to change a thing but you might make it clear by explicitly returning your custom SaveResult:
readonly MyCustomEFContextProvider _contextProvider = new MyCustomEFContextProvider();
...
[HttpPost]
public CustomSaveResult SaveChanges(JObject saveBundle) {
return _contextProvider.SaveChanges(saveBundle);
}
JSON.Net will happily serialize the usual material + your custom ErrorEntities property (be sure to make it serializable!) and send it to the Breeze client.
On the Breeze client you write your own variation on the stock Breeze Web API data service adapter. Yours does almost exactly the same thing as the Breeze version. But, when processing the save payload from the server, it also extracts this extra "error entities" material in the response and does whatever you want to do with it.
I don't know what that will be but now you have it.
See how easy that was? LOL.
Breeze does not currently support a save mechanism that both saves and returns an error at the same time. While possible this seems a bit baroque.
As you pointed out, you can
1) Throw an exception inside of the BeforeSaveEntities and fail the save. You can even specify which specific entity or entities caused the failure and why. In this case the entire save is aborted.
or
2) Remove 'bad' items from the saveMap within the BeforeSaveEntities and save only a subset of what was passed in. In this case you are performing a partial save.
But we don't support a hybrid of these two. Please add this to the Breeze User Voice if you feel strongly and we can see if other members of the community feel that this would be useful.

ASP.Net MVC 3: Where to handle session loss?

I've started bumping into errors when my session has been lost, or upon rebuilding my project, as my forms authentication cookie still lives on.
In WebForms I'd use the masterpage associated with pages which require login to simply check for the session.
How would I do this in one location in MVC ? I'd hate having to check for session state in every action in my controllers.
On the other hand I can't just apply a global filter either, since not all Controllers require session state.
Would it perhaps be possible in my layout view ? It's the only thing the pages which require session have in common.
One thing you could do is to sub-class the controllers that do need session state. This way you could create a filter on just this base controller. This would allow you to do it all in one place. Plus, as you pointed out, a global filter won't help you here since the logic does not apply to every controller.
add it to session start. if a session loss happens it needs to trigger a session start too. you can handle it in there as follows:
protected void Session_Start(object src, EventArgs e)
{
if (Context.Session != null)
{
if (Context.Session.IsNewSession)
{
string sCookieHeader = Request.Headers["Cookie"];
if ((null != sCookieHeader) && (sCookieHeader.IndexOf("ASP.NET_SessionId") >= 0))
{
// how to simulate it ???
// RedirectToAction(“ActionName”, “ControllerName”, route values);
Response.Redirect("/Home/TestAction");
}
}
}
}
I agree with what Steve has mentioned, but I suggest to use Global Filters instead of creating a base class for all your controllers. The reason for this is everytime you create a new controller, you should always remember to derive from the base controller or you may experience random behaviours in your application that may take you hours of debugging. This is especially important when you stop development for a while and then get back to it.
Also, another reason is the "Favour composition over inheritance" principle.

Prism 2.1 Publish/Subscribe with weak reference?

I am building a Prism 2.1 demo by way of getting up to speed with the technology. I am having a problem with CompositePresentationEvents published and subscribed via the Event Aggregation service. The event subscription works fine if I set a strong reference (KeepSubscriberReferenceAlive = true), but it fails if I set a weak reference (KeepSubscriberReferenceAlive omitted).
I would like to subscribe with a weak reference, so that I don't have to manage unsubscribing from the event. Is there any way to do that? Why is a strong reference required here? Thanks for your help!
Here are the particulars: My demo app is single-threaded and has two regions, Navigator and Workspace, and three modules, NavigatorModule, WorkspaceAModule, and WorkspaceBModule. The NavigatorModule has two buttons, 'Show Workspace A' and 'Show Workspace B'. When one of these buttons is clicked, an ICommand is invoked that publishes a CompositePresentationEvent called ViewRequested. The event carries a string payload that specifies which workspace module should be shown.
Here is the event declaration, from the app's Infrastructure project:
using Microsoft.Practices.Composite.Presentation.Events;
namespace Prism2Demo.Common.Events
{
public class ViewRequestedEvent : CompositePresentationEvent<string>
{
}
}
Here is the event publishing code, from the Navigator module:
// Publish ViewRequestedEvent
var eventAggregator = viewModel.Container.Resolve<IEventAggregator>();
var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
viewRequestedEvent.Publish(workspaceName);
Here is the event subscription code, which each Workspace module includes in its Initialize() method:
// Subscribe to ViewRequestedEvent
var eventAggregator = m_Container.Resolve<IEventAggregator>();
var viewRequestedEvent = eventAggregator.GetEvent<ViewRequestedEvent>();
viewRequestedEvent.Subscribe(this.ViewRequestedEventHandler, ThreadOption.PublisherThread, true);
The Subscribe() statement is shown with a strong reference.
Thanks again for your help.
A couple of things to check:
Make sure that your EventAggregator instance is being correctly registered with the container or it may itself be garbage collected:
container.RegisterType<IEventAggregator, EventAggregator>(new ContainerControlledLifetimeManager());
Also make sure that you have a strong reference to the subscribed object held somewhere (this in your subscription code).

Resources