FormFlow: add multiple entities using recurring questions - c#

I've been playing around with the bot framework and creating a chat bot for fun that lets you detail the members of your family/pets.
Is there a way to recur over the same set of questions until the user is satisfied? Example code below:
[Prompt("What is your family name?")]
public string familyName{ get; set; }
[Prompt("What is your postcode?")]
public string postcode { get; set; }
[Prompt("Would you like to add a family member? {||}")]
public bool AddPerson { get; set; }
[Prompt("What is their name?")]
public string PersonName { get; set; }
[Prompt("How old are they?")]
public string PersonAge{ get; set; }
[Prompt("How are they related to you?")]
public string PersonRelation{ get; set; }
[Prompt("Would you like to add another family member? {||}")]
public bool addAnotherPerson { get; set; }
public IForm<Family> BuildForm()
{
return new FormBuilder<GetQuoteDialog>()
.Field(nameof(familyName))
.Field(nameof(postcode))
//Choose to add a person to the family
.Field(nameof(AddPerson))
//Details of that person.
.Field(new FieldReflector<Family>(nameof(PersonName))
.SetActive((state) => state.AddPerson== true))
.Field(new FieldReflector<Family>(nameof({PersonAge))
.SetActive((state) => state.AddPerson== true))
.Field(new FieldReflector<Family>(nameof({PersonRelation))
.SetActive((state) => state.AddPerson== true))
//Prompts the user to add another if they wish
//Recurs to the PersonName field and lets them go through the
//process of adding another member
.Field(new FieldReflector<Family>(nameof({AddAnotherMember))
.SetActive((state) => state.AddPerson== true))
.Confirm("Is this your family? {*}")
.Build();
}
}
Does anyone have an idea on how to accomplish this?
I call the formflow like this:
public async Task confirmAdd(IDialogContext context, IAwaitable<bool> result)
{
if (await result)
{
// builds and calls the form from here
var myform = new FormDialog<BuildFamily>(new BuildFamily(), BuildForm, FormOptions.PromptInStart, null);
context.Call<BuildFamily>(myform, End);
}
}
private async Task End(IDialogContext context, IAwaitable<BuildFamily> result)
{
BuildFamily data = null;
try
{
data = await result;
await context.PostAsync("Nice family you got there :)");
}
catch (OperationCanceledException)
{
await context.PostAsync("You canceled the form!");
return;
}
}

I'm not sure how to "recur over the same set of questions until the user is satisfied" within a FormFlow dialog. However, you can ask the user the question "Would you like to add more family members?" in the calling dialog, and achieve the same type of conversation flow. Remove the PostalCode and FamilyName type questions, and put them in a separate dialog. Then, in the add family members dialog do something like this:
[Serializable]
public class AddFamilyMembersDialog : IDialog<object>
{
List<Family> _familyMembers = new List<Family>();
public Task StartAsync(IDialogContext context)
{
context.Wait(MessageReceivedAsync);
return Task.CompletedTask;
}
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
PromptAddMembers(context);
}
private void PromptAddMembers(IDialogContext context)
{
PromptDialog.Text(context, AfterPromptAdd, "Would you like to add more family members?", null, 1);
}
private async Task AfterPromptAdd(IDialogContext context, IAwaitable<string> result)
{
var yesno = await result;
if (yesno.ToLower() == "yes")
{
await context.Forward(FormDialog.FromForm(Family.BuildForm), AfterAdded, null, CancellationToken.None);
}
else
{
//_familyMembers contains everyone the user wanted to add
context.Done(true);
}
}
private async Task AfterAdded(IDialogContext context, IAwaitable<Family> result)
{
var member = await result;
if (member != null)
_familyMembers.Add(member);
PromptAddMembers(context);
}
[Serializable]
public class Family
{
[Prompt("What is their name?")]
public string PersonName { get; set; }
[Prompt("How old are they?")]
public string PersonAge { get; set; }
[Prompt("How are they related to you?")]
public string PersonRelation { get; set; }
public static IForm<Family> BuildForm()
{
return new FormBuilder<Family>()
.AddRemainingFields()
.Build();
}
}
}

Related

Can't get the state after signing in with fluxor an Blazor

I want to implement state management into an application Blazor assembly, so I added Fluxor as library.
I've created an abstract root state which contain the shared propreties:
public abstract class RootState
{
public RootState(bool isLoading, string currentErrorMessage)
=> (IsLoading, CurrentErrorMessage) = (isLoading, currentErrorMessage);
public bool IsLoading { get; }
public string CurrentErrorMessage { get; }
public bool HasCurrentErrors => !string.IsNullOrWhiteSpace(CurrentErrorMessage);
}
Then, I created the Login State which inherits from Rootstate:
public class LoginState : RootState
{
public bool IsAuthenticated { get; set; }
public LoginResponseDto UserData { get; set; }
public LoginState(bool isLoading, string currentErrorMessage, bool isAuthenticated, LoginResponseDto userData)
:base(isLoading, currentErrorMessage)
{
IsAuthenticated = isAuthenticated;
UserData = userData;
}
}
After that, I created the feature class :
public class LoginFeature : Feature<LoginState>
{
public override string GetName() => nameof(LoginState);
protected override LoginState GetInitialState()
=> new LoginState(false, null, false, null);
}
Then the actions:
public class LoginAction
{
public LoginDto LoginDto { get; }
public LoginAction(LoginDto LoginDto)
{
this.LoginDto = LoginDto;
}
}
public class LoginAccessFailure : FailureAction
{
public LoginAccessFailure(string errorMessage)
:base(errorMessage)
{
}
}
public abstract class FailureAction
{
public string ErrorMessage { get; }
protected FailureAction(string errorMessage) => ErrorMessage = errorMessage;
}
After that I created the Reducer and Effect:
public class LoginReducer
{
[ReducerMethod]
public static LoginState ReduceLoginAction(LoginState state, LoginAction action)
=> new LoginState(true, null, false, state.UserData);
[ReducerMethod]
public static LoginState ReduceLoginSuccessAction(LoginState state, LoginActionSuccess action)
{
Console.WriteLine("State from Reducer", action);
var result = new LoginState(false, null, action.IsAuthenticated, action.UserData);
Console.WriteLine("Result from Reducer", result);
return result;
}
[ReducerMethod]
public static LoginState ReduceLoginFailureAction(LoginState state, LoginAccessFailure action)
=> new LoginState(false, action.ErrorMessage, false, null);
}
Effect:
public class LoginEffect : Effect<LoginAction>
{
private readonly IAccountService _accountService;
public LoginEffect(IAccountService accountService)
{
_accountService = accountService;
}
public override async Task HandleAsync(LoginAction action, IDispatcher dispatcher)
{
try
{
var loginResponse = await _accountService.Login(action.LoginDto);
await Task.Delay(TimeSpan.FromMilliseconds(1000));
dispatcher.Dispatch(new LoginActionSuccess(loginResponse, true));
}
catch (Exception e)
{
dispatcher.Dispatch(new LoginAccessFailure(e.Message));
}
}
}
When I want to call the dispatcher with this instruction dispatcher.Dispatch(new LoginAction(new LoginDto { Email = "test#email.com", Password = "test" })); I've got the result in the DevTools but there is not data in IState<LoginState> so I can't access to the state.
There is any error on my code please ?
Things to check
You have [Inject] before your private IMyState<T> MyState { get; set; }
You have a <StoreInitializer/> component in your main app component.
Your component descends from FluxorComponent or you manually subscribe to MyState.StateChanged and call InvokeAsync(StateHasChanged)

Saving a List to SQLite.NET using SQLiteAsyncConnection

Working on a project thats Stores items to my sqlDb created it following this video by James Montemagno https://www.youtube.com/watch?v=XFP8Np-uRWc&ab_channel=JamesMontemagno my issue now comes when I'm trying to save a list to the sqlDb it shows that it was added however when i retrieve my data my List prop is null.
public class UserTask{
[PrimaryKey]
public string ID { get; set; }
public string Title { get; set; }
[TextBlob("TagBlobbed")]
public List<string> TagsList { get; set; }
public string TagBlobbed { get; set; }
public string Details { get; set; }
[Ignore]
public string Comment { get; set; }
public UserTask()
{
TagsList = new();
}
}
public static class PlannerDataService
{
static SQLiteAsyncConnection db;
static async Task Init()
{
if (db != null) return;
var databasePath = Path.Combine(FileSystem.AppDataDirectory, "DbTasks.db");
db = new SQLiteAsyncConnection(databasePath);
await db.CreateTableAsync<UserTask>();
}
public static async Task AddNewTask(UserTask t)
{
await Init();
var task = new UserTask()
{
ID = t.ID,
TagsList = t.TagsList,
Details = t.Details,
Title = t.Title
};
await db.InsertAsync(task);
}
public static async Task<List<UserTask>> GetUserTasks()
{
await Init();
var tasks = await db.Table<UserTask>().ToListAsync();
var t = tasks.OrderByDescending(a => a.ID).ToList();
return t;
}
public static async Task RemoveTask(string id)
{
await Init();
await db.DeleteAsync<UserTask>(id);
}
public static async Task UpdateTask(UserTask t)
{
await Init();
var task = new UserTask()
{
ID = t.ID,
TagsList = t.TagsList,
Details = t.Details,
Title = t.Title
};
await db.UpdateAsync(task);
}
}
I've seen + read questions similar to this and I've tried following their advice to no luck which is why I'm posting for a better solution without changing much of my code.

How can I call a method as background worker by adding it into Startup in .netcore 2.1?

I am creating a simple logging system by using Nest and C#. I have a log producer for collecting logs inside of blockingcollection. Also I have a consumer. But I stumpled upon with an issue. How can I use my comsumer in startup or is there any way to create background servise which was listening queue of blockingcollection? What is best practice of it? I am confusing how to call AsyncConsumer or consumer when application startup.
public class SimpleLog
{
public string Header { get; set; }
public string LogDate { get; set; }
public string Sessionid { get; set; }
public string Userid { get; set; }
public string Correlationid { get; set; }
public int Status { get; set; }
public string UrlQueryString { get; set; }
public string UrlPath { get; set; }
public string UrlMethod { get; set; }
public string Environment { get; set; }
public string IndexName { get; set; }
public string IndexType { get; set; }
}
public class QuickLog
{
private static BlockingCollection<SimpleLog> data = new BlockingCollection<SimpleLog>();
public static void Producer(SimpleLog pageviewLog)
{
data.TryAdd(pageviewLog, TimeSpan.FromSeconds(10));
}
public static void Consumer()
{
var _client = ElasticConfig.GetClient();
var logs = new List<SimpleLog>();
foreach (var item in data.GetConsumingEnumerable())
{
logs.Add(item);
}
if (logs == null && logs.Count <= 0)
return;
var log = logs.FirstOrDefault();
var response = _client.IndexMany(logs, log.IndexName, log.IndexType);
if (!response.IsValid)
throw response.OriginalException;
}
public async Task AsyncConsumer()
{
var _client = ElasticConfig.GetClient();
var logs = new List<SimpleLog>();
foreach (var item in data.GetConsumingEnumerable())
{
logs.Add(item);
}
if (logs == null && logs.Count <= 0)
return;
var log = logs.FirstOrDefault();
var response = await _client.IndexManyAsync(logs, log.IndexName, log.IndexType).ConfigureAwait(false);
if (!response.IsValid)
throw response.OriginalException;
await Task.Delay(TimeSpan.FromSeconds(10)).ConfigureAwait(false);
}
}
public static class ElasticConfig
{
private static IElasticClient _client;
static ElasticConfig()
{
var esurl = LogSettings.Url;
string[] urls = esurl.Split(',');
var nodes = new Uri[2];
for (int i = 0; i < urls.Length; i++)
{
nodes.SetValue(new Uri(urls[i]), i);
}
var connectionPool = new SniffingConnectionPool(nodes);
var connectionSettings = new ConnectionSettings(connectionPool).RequestTimeout(
TimeSpan.FromSeconds(60))
.PingTimeout(TimeSpan.FromSeconds(60))
.MaxRetryTimeout(TimeSpan.FromSeconds(60))
.MaxDeadTimeout(TimeSpan.FromSeconds(60))
.DeadTimeout(TimeSpan.FromSeconds(60)).DisablePing()
.SniffOnConnectionFault(false)
.SniffOnStartup(false)
.SniffLifeSpan(TimeSpan.FromMinutes(1));
_client = new ElasticClient(connectionSettings);
}
public static IElasticClient GetClient()
{
return _client;
}
}
Not sure how many times and which method exactly you want to call. If you want to run some asynchronous background jobs you can use IHostedService. You will need to install Microsoft.Extensions.Hosting NuGet package or Microsoft.AspNetCore.App metapackage.
Usage:
Add this line to your Startup.cs
services.AddHostedService<LogBackgroundService>(); //service is instance of IServiceCollection
And this is the implementation of your background service:
public class LogBackgroundService : IHostedService
{
public async Task StartAsync(CancellationToken cancellationToken)
{
await QuickLog.AsyncConsumer(); // or whatever you want to call
}
public Task StopAsync(CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Behavior:
It will run once(but IHostedService still will be running. If you want to reduce resource consumption just call StopAsync() when it's done). If you want to run something in a loop, you can implement this:
while (!cancellationToken.IsCancellationRequested)
{
await QuickLog.AsyncConsumer();
await Task.Delay(250, cancellationToken); // you can add this if you want to throttle
}
PS. If you need to run multiple IHostedServices in your application without blocking each other you will need to wrap your methods into Tasks:
public Task StartAsync(CancellationToken cancellationToken)
{
Task.Run(() => QuickLog.AsyncConsumer(), cancellationToken);
}
IHostedService is solution for you.
You can crate new class and inherit from this class https://gist.github.com/davidfowl/a7dd5064d9dcf35b6eae1a7953d615e3
Then your new class will be something like this
public class LogService : HostedService
{
private readonly IServiceScopeFactory _scopeFactory;
public LogBackgroundService (IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await new QuickLog().AsyncConsumer(cancellationToken);
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
}
}
}
Finally update your Startup.cs:
services.AddSingleton<IHostedService, LogService>();

Skips questions depending on a choice

I would like to know if it is possible that, depending on the user's choice, all the questions may be skipped and the dialogue terminated.
Example, I have the next code:
public ContentClassification ContentClassification {get;set;}
public StatusOfContent StatusContent {get; set;}
public Accessibility ExternalSharing {get; set;}
Depending on the choice of "ContentClassification", skip the other questions.
Thanks in advance.
Depending on the choice of "ContentClassification", skip the other questions.
You can use FieldReflector to implement your own IField, for example:
public enum ContentClassification
{
Confidential_Restricted = 1,
Confidential_Secret = 2,
Public = 3,
Strictly_Confidential = 4,
help = 5
};
public enum StatusContent
{
Status1,
Status2
}
public enum Accessibility
{
Accessibility1,
Accessibility2
}
[Serializable]
public class Classification
{
public ContentClassification? Choice { get; set; }
public StatusContent? StatusOfContent { get; set; }
public Accessibility? Accessibility { get; set; }
public static bool Confirmation = true;
public static IForm<Classification> BuildForm()
{
return new FormBuilder<Classification>()
.Message("You want to")
.Field(new FieldReflector<Classification>(nameof(Choice))
.SetNext((value, state) =>
{
var selection = (ContentClassification)value;
if (selection == ContentClassification.help)
{
Confirmation = false;
state.Accessibility = null;
state.StatusOfContent = null;
}
else
{
Confirmation = true;
}
return new NextStep();
}))
.Field(new FieldReflector<Classification>(nameof(StatusOfContent))
.SetActive(state => Confirmation))
.Field(new FieldReflector<Classification>(nameof(Accessibility))
.SetActive(state => Confirmation))
.Build();
}
}
And in the RootDialog:
[Serializable]
public class RootDialog : IDialog<object>
{
public Task StartAsync(IDialogContext context)
{
var form = new FormDialog<Classification>(new Classification(), Classification.BuildForm, FormOptions.PromptInStart, null);
context.Call(form, this.GetResultAsync);
return Task.CompletedTask;
}
private async Task GetResultAsync(IDialogContext context, IAwaitable<Classification> result)
{
var state = await result;
//TODO:
}
}
Using this code, when user select Help in the first dialog Choice, it will skip the following two questions and you will get the result in GetResultAsync with Choice = Help, StatusOfContent = null, Accessibility = null and so on.

MVVM Light execute method in external library

I try to execute a method in an external library:
This is the interface of the external library:
public interface IExternal
{
string Name { get; }
Task DoAsync();
}
This is the class of the external library:
public class ExternalClass : IExternal
{
public string Name
{
get
{
return "Test external";
}
}
public async Task DoAsync()
{
Console.WriteLine("Do async");
await Task.Delay(3000);
Console.WriteLine("Done async");
}
}
This is the local class my ViewModel uses:
public class External
{
public string Name { get; set; }
public string Description { get; set; }
public RelayCommand Run { get; set; }
}
In my application i load the external library as an reference.
An try to bind DoAsync as RelayCommand:
foreach (var item in externals)
{
var t = new External();
t.Name = item.Name;
t.Description = "test";
t.Run = new RelayCommand(async () => await item.DoAsync());
}
The XAML binding of the button is as follows: Command="{Binding Run}"
Nothing happens, no command is executed.

Categories