Set formflow dialog field value without getting prompted later - botframework

I'm using SetDefine() to provide a value for one of the fields on my bot dialog..
return builder
.Field(new FieldReflector<CarValuationDialog>(nameof(UserName))
.SetDefine(async (state, field) =>
{
field.SetValue(state, userName);
return await Task.FromResult(true);
}))
userName is just a variable in the function that's calling the return builder line. The property UserName is defined as..
public string UserName { get; set; }
The issue I have is that, when I run the bot in the emulator, the first thing I see if this..
How can I configure the property UserName so that it does not get prompted for a value in the bot?

Since you are defining your username field and you don't want the bot the prompt for that field you can use .SetActive
.Field(new FieldReflector<CarValuationDialog>(nameof(UserName))
.SetDefine(async (state, field) =>
{
field.SetValue(state, "username");
return await Task.FromResult(true);
})
.SetActive((state) => String.IsNullOrEmpty(state.UserName)))
So a prompt will be only initiated if the field is Null or empty. You can try out other functions which return back a bool to match your usecase better.

Create a new class that holds all of the items you want in the form. Then create a static method in that class that returns an IForm<class>.
[Serializable]
public class CallNotesForm
{
[Prompt("What is the subject of the call?")]
public string Subject { get; set; }
[Prompt("What are the call details?")]
public string Notes { get; set; }
public static IForm<CallNotesForm> BuildForm()
{
return new FormBuilder<CallNotesForm>()
.Message("Please enter some details about your call. Enter 'Help' for more information")
.Build();
}
}
Then in your method that calls the form,
IForm<CallNotesForm> formVM = new CallNotesForm() { Direction = CallNotes.CallDirection.Outgoing };
IFormDialog<CallNotesForm> form = new FormDialog<CallNotesForm>(formVM, CallNotesForm.BuildForm, FormOptions.PromptInStart);
context.Call(form, CallDetailsAsync);

Related

MudBlazor MudAutocomplete - how to show 'name's in the list, but bind an Id?

My model looks like this
public partial class EditModel
{
public int Id { get; set; }
...
public string Item { get; set; }
}
My SearchItems method header looks like this
protected async Task<IEnumerable<ListItem>> SearchItems(string value)
which returns 'list' of these
public partial class ListItem
{
public string Id { get; set; }
public string Name { get; set; }
}
How do I get my MudAutocomplete to show the Name, yet return/bind the Id?
<MudAutocomplete T="ListItem" Label="Select item" #bind-Value="EditModel.Item"
Clearable="true"
MinCharacters="4" SearchFunc="#SearchItems"
ToStringFunc="#(i => i==null ? null : $"{i.Id} [{i.Name}]")"
SelectValueOnTab="true"/>
on the #bind-Value, Visual studio shows this error
...cannot convert from 'string' to 'EditModel.Item'
This is how I solved it for now...
My SearchItems method now just returns a list of string
protected async Task<IEnumerable<string>> SearchItems(string value)
I've put this attribute in the MudAutocomplete
ToStringFunc="#(i => ItemDisplay(i))"
This is my ItemDisplay method
private string ItemDisplay(string itemId)
{
var item = ListItems.FirstOrDefault(i => i.Id == itemId);
return item == null ? "!Not Found!" : $"{item.Id} [{item.Name}]";
}
I've had to add this to my ComponentBase, to 'cache' all the ListItems for use in ItemDisplay() method:
public List<ListItem> ListItems { get; set; } = new();
In OnInitializedAsync()
ListItems = await MyService.GetItemsAsync();
I've set up my GetItemsAsync() to use IMemoryCache (Microsoft.Extensions.Caching.Memory), but I still don't like this approach. I find it difficult to believe that this component does not support the feature.
Maybe the component is updated but I was able to achieve this by using the following approach which I think is good.
The model you want to use
record State(Guid Id, string Name);
The binding value
private State value1;
The search function returns IEnumerable<State>
private async Task<IEnumerable<State>> Filter(string value)
{
// Filtering logic
}
Finally, I am using ToStringFunc to define how values are displayed in the drop-down list
<MudAutocomplete T="State" ToStringFunc="#(state => state.Name)" Label="US States" #bind-Value="value1" SearchFunc="#Filter" Variant="Variant.Outlined"/>

Pass complex object between action methods ASP.Net MVC Core

I studied most of the similar questions but couldn't find the answer!
Let me declare that I can define a simple TempData like an string,int and get it in another action method successfully, but
I have 3 parameters/variable in ActionMethod1 which is named "ExternalLoginCallBack", and need to ask a "UserName" from user on client side and then save the total 4 parameters in ActionMethod2 which is named "CreateExternalUser".
This is what I have.
AskUserNameView_NoActionMethod.cshtml as below:
#model ExternalUserViewModel
<form asp-action="CreateExternalUser" asp-controller="Account" method="post">
<label asp-for="UserName">Input your UserName Here: </label>
<input asp-for="UserName" >
<input type="submit" value="ُSubmit"/>
</form>
(Using TempData) Attemp-No.1:
public async Task<IActionResult> ActionMethod1
//some codes here
var externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync();
TempData["externalLoginInfo"] = externalLoginInfo;
TempData["email"] = email;
TempData["returnUrl"] = returnUrl;
return View("AskUserNameView_NoActionMethod");
but instead of showing the AskUserNameView_NoActionMethod.cshtml it shows just a white page with no errors,no exception and nothing :
Attemp-No2: I removed "ExternalLoginInfo" type and only two simple string as an object remained to pass to ActionMethod2:
public async Task<IActionResult> ActionMethod1
//some codes here
var externalUserViewModel= new ExternalUserViewModel()
{
Email = email,
ReturnUrl = returnUrl,
};
TempData["externalUserViewModel"] = externalUserViewModel;
return View("AskUserNameView_NoActionMethod");
but again the white page above appeared. When I remove the complex TempData, my AskUserNameView_NoActionMethod.cshtml rendered successfully, and I can pass UserName which is entered by client side, to ActionMethod2. But without 3 other parameters which is needed to create a new External user !!
My ExternalUserViewModel is as below:
public class ExternalUserViewModel
{
public ExternalLoginInfo ExternalLoginInfo { get; set; }
public string Email { get; set; }
public string UserName { get; set; }
public string ReturnUrl { get; set; }
}
and already added these codes to startup.cs:
services.AddControllersWithViews();
services.AddSingleton<ITempDataProvider, CookieTempDataProvider>();
app.UseSession();
If TempData is not apllicable, I tried to pass my model with 3 parameters (Email,ReturnUrl,ExternalLoginInfo ) to strongly typed AskUserNameView_NoActionMethod.cshtml, but again Username entered by client side,Email and returnedUrl passed to ActionMethod2 but externalLoginInfo was null .
Summary: need an example to pass a complex data/object from actionmethod1 to actionmethod2 , without redirect to actionmethod2 !!
You have two methods to achieve it.
As the TempData cannot store complex object here ,you can Serialize the object to json string and store it in TempData, then you can get the json string in CreateExternalUser action and Deserialize this json string to the correspond object as follow:
public async Task<IActionResult> ActionMethod1()
{
//some codes here
var externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync();
TempData["externalLoginInfo"] = JsonConvert.SerializeObject(externalLoginInfo);
TempData["email"] = email;
TempData["returnUrl"] = returnUrl;
return View("AskUserNameView_NoActionMethod");
}
Receive:
public async Task<IActionResult> CreateExternalUser(UserName userName)
{
//some codes here
var externalLoginInfo = JsonConvert.DeserializeObject<ExternalLoginInfo>(TempData["externalLoginInfo"].ToString());
var email = TempData["email"] as string;
var returnUrl = TempData["returnUrl"] as string;
return View();
}
Another method is to create a custom method named TempDataExtensions to pass object from an action to another.
public static class TempDataExtensions
{
public static void Put<T>(this ITempDataDictionary tempData, string key, T value) where T : class
{
tempData[key] = JsonConvert.SerializeObject(value);
}
public static T Get<T>(this ITempDataDictionary tempData, string key) where T : class
{
object o;
tempData.TryGetValue(key, out o);
return o == null ? null : JsonConvert.DeserializeObject<T>((string)o);
}
}
Store object:
var externalLoginInfo = await _signInManager.GetExternalLoginInfoAsync();
TempData.Put("externalLoginInfo", externalLoginInfo);
Get object:
var externalLoginInfo = TempData.Get<ExternalLoginInfo>("externalLoginInfo");

LINQ query fails when I run it from an Azure Function, but not from a console app

I'm moving some code from a traditional worker role to an Azure Function. I've found a line of code that returns a result when I call it from a console app, but null when I call it from a function.
Now, for some example code. I wrote a _resultProvider class that basically queries an underlying CosmosDB database -- at the base class, it creates an IOrderedQueryable query and filters it based on the predicate that you pass in as a parameter. The first line of code returns a result only when I call it from a console app, and null if I call it from an Azure Function. The second line returns a result from either platform.
Gets result when called from the worker role, but null when called from the function:
var res1 = _resultProvider.GetSpecialAsync(o => id == o.Id).Result.FirstOrDefault();
Gets result from either the worker role or the function:
var res2 = _resultProvider.GetSpecialAsync(o => 1 == 1).Result.Where(o=>id==o.Id).FirstOrDefault();
I'm guessing this is some kind of LINQ issue, because passing the predicate along doesn't seem to work from the function, but it works if I just get all the results and query that result set.
Here's the GetSpecialAsync code:
public async Task<IEnumerable<T>> GetItemsSpecialAsync(Expression<Func<T, bool>> predicate)
{
IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
new FeedOptions { MaxItemCount = -1, EnableCrossPartitionQuery = true})
.Where(predicate)
.AsDocumentQuery();
List<T> results = new List<T>();
while (query.HasMoreResults)
{
results.AddRange(await query.ExecuteNextAsync<T>());
}
return results;
}
Here's the type I'm attempting to return, ResultDocVm:
public class ResultDocVm : DocViewModelBase
{
public string Name { get; set; }
public long AccountId { get; set; }
// ... insert more junk here with getters and setters
}
Here's DocViewModelBase:
public abstract class DocViewModelBase
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
public DateTime? CreatedAt { get; set; }
//... even more junk here
}
So after all the back and forth it looks like the Console App is taking the JsonProperty attribute into account while the Azure Function doesn't.
This generates a query which will return no results because the Id property will be uppercased and not lowercased ie id.
It sounds like a bug with the Azure Function at the Azure level and not with your code per se.

Entity Framework Many to Many query

I want to write a simple query, but there are some problems.
I have 2 tables M to N:
Users -> Events.
I want to get all users of a specific event (get this event by eventId).
public IQueryable<User> GetUsersByEventId(int eventId)
{
IQueryable<User> query = this.Context.Users.AsQueryable();
return query.Where(x => x.Events.SingleOrDefault(e => e.EventId == eventId)); ??
}
Something is missing and I dont know what, can someone help me? Thanks a lot !
If I understand you correctly (adding your models would help), I think you want Any
public IQueryable<User> GetUsersByEventId(int eventId)
{
return Context.Users
.Where(u => u.Events.Any(e => e.EventId == eventId));
}
This should return all users who have any event matching the given id.
Note: If you set up your relationships correctly, you should be able to get this directly from the Event.
public class Event
{
...
public virtual ICollection<User> Users { get; set; }
}
So then, you'd get the Event by id and access it's user collection.
var evt = repo.GetEventById(id);
var users = evt.Users;
I suggest you do that in your Event model itself. AFAIK you are using Event, User and EventUsers tables which is standard stuff for many2many.
public class Event
{
public int Id { get; set; }
// ...
public virtual ICollection<EventUsers> EventUsers { get; set; } // This is table that holds EventId, UserId (many2many)
public IQueryable<User> Users { get { return this.EventUsers.Select(x => x.User); } } // Get all users that are in this event
}

Sharing model objects between controller actions in MVC3

I have two actions in a controller. One that displays a form for file upload and another one that displays the results of the upload.
I have created a POCO called FileInfo i.e
public class FileInfo
{
public string Name { get; set; }
public int Length { get; set; }
public string FileType { get; set; }
public string ErrorMessage { get; set; }
}
When I submit the form, the Upload action creates and populates the FileInfo object and then redirects to the second action called results. I want to be able to use the same file info object in the results action.
I am able to get around this using TemPData[], but it is limited since it only holds object data for a single request. I presume there must be a better way to share abjects between controller actions.Any help is appreciated!
// Upload Action
List<FileInfo> fileInfo= new List<FileInfo>();
//populate the fileInfo object using fi.Add()
if ((status.ToString() == "OK"))
{
TempData["Info"] = fileInfo;
return RedirectToAction("Results");
}
else
{
return RedirectToAction("Index");
}
//Results action.
public ActionResult Results()
{
List<FileInfo> fi = TempData["Info"] as List<FileInfo>;
if (fi != null)
{
return View(fi);
}
else
{
return View("Index");
}
}
If you need something to stick around longer then one subsequent request, you will have to put it in Session or in persistent storage (e.g. database).

Resources