Due to lack of tutorials and information i am unable to find how i can save the information in bots. Lets say i ask user to make a selection like this:
public enum City
{
Cleveland, Columbus, Kentucky, Mason, Akron
};
[Serializable]
public class SandwichOrder
{
[Prompt("Please select what {&} you are in? {||}")]
public City? City;
public static IForm<SandwichOrder> BuildForm()
{
return new FormBuilder<SandwichOrder>()
.Message("Welcome to the my bot!")
.Build();
}
};
I just want to ask for city once how can i do that? How can i preserve the value of user selection and only call this method if it is first user interaction.
Controller class:
internal static IDialog<SandwichOrder> MakeRootDialog()
{
return Chain.From(() => FormDialog.FromForm(SandwichOrder.BuildForm));
}
[ResponseType(typeof(void))]
public virtual async Task<HttpResponseMessage> Post([FromBody] Activity activity)
{
if (activity != null)
{
// one of these will have an interface and process it
switch (activity.GetActivityType())
{
case ActivityTypes.Message:
await Conversation.SendAsync(activity, MakeRootDialog);
break;
}
}
var response = Request.CreateResponse(HttpStatusCode.OK);
return response;
}
The SDK includes several ways of persisting data relative to a user or conversation:
userData stores information globally for the user across all conversations.
conversationData stores information globally for a single conversation. This data is visible to everyone within the conversation so care should be used to what’s stored there. It’s disabled by default and needs to be enabled using the bots persistConversationData setting.
privateConversationData stores information globally for a single conversation but its private data for the current user. This data spans all dialogs so it’s useful for storing temporary state that you want cleaned up when the conversation ends.
dialogData persists information for a single dialog instance. This is essential for storing temporary information in between the steps of a waterfall.
Bots built using Bot Builder are designed to be stateless so that they can easily be scaled to run across multiple compute nodes. Because of that you should generally avoid the temptation to save state using a global variable or function closure. Doing so will create issues when you want to scale out your bot. Instead leverage the data bags above to persist temporary and permanent state.
More info here:
https://docs.botframework.com/en-us/node/builder/guides/core-concepts/#adding-dialogs-and-memory
Related
This is a more general question of how bots work more then a specific coding question but bear with me.
With my current bot when a users first enters a page on a website all of their answers are filtered by "king-kingvalue", then if they go to another page it will change to "queen-queenvalue". The problem is that if UserA is on the page that sets their filter to "queen-queenvalue", and then UserB goes to the page with the "king-kingvalue" filter, UserA's filters are also set to "king-kingvalue".
So, my question is where/when are separate conversations for each user being established within the code?
From my understanding the bot..
-->gets the filter from the website and passes it to the BotController, where the PostAsync is called.
public class BotController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter _adapter;
private readonly IBot _bot;
public BotController(IBotFrameworkHttpAdapter adapter, IBot bot)
{
_adapter = adapter;
_bot = bot;
}
[HttpPost]
public async Task PostAsync()
{
// Delegate the processing of the HTTP POST to the adapter.
// The adapter will invoke the bot.
await _adapter.ProcessAsync(Request, Response, _bot);
}
-->Makes a new instance of the Bot, which in turn creates a new Conversation and User State.
public class QnABot : DialogBot<QnADialog>
{
public QnABot(ConversationState conversationState, UserState userState, IQnAService qnaService, ILogger<QnABot> logger)
: base(conversationState, userState, new QnADialog(qnaService), logger)
{
}
-->And at this point each new user should have their own unique conversation with the bot, yes?
However, this does not seem to be the case for me because code that edits and saves the Conversation and User States will effect every conversation, not just the one the changes are being made in.
Any ideas/solutions would be much appreciated.
#tdurnford you are right. I had been checking just for the users name, but as the issue you linked said it seems that it is the users id is what the bot needs to distinguish between multiple users. Thank you.
First of all, I couldn't make the title more explanatory, I will try to lay out the problem then provide my solution for it
I'm implementing a backend in asp core for our game, we have few requests that are somewhat large, like requesting the items we provide in the store, every user starts the game loads the store info which makes a database trip to pull the entire store info, which RARELY change -less than once a month-, so we are making thousands of database trip that aren't needed.
on top of that we return timestamps for when was the last time an item image has changed, the images are stored in a blob which makes me query the blob for change date, which makes the request way costlier
so to solve all of this, I implemented a small class to cache the request until we need to update it,for this request and some others, but I'm not sure if I'm looking at this correctly
here is the base abstract class:
public abstract class CachedModel<T>
{
protected T Model { get; set; }
private readonly SemaphoreSlim semaphore = new SemaphoreSlim(1,1);
protected abstract Task ThreadSafeUpdateAsync();
protected abstract bool NeedsUpdate();
public async Task<T> GetModel()
{
if (NeedsUpdate())
{
try
{
await semaphore.WaitAsync();
if(NeedsUpdate()) // not sure if this is needed, can other threads enter here after the first one already updated the object?
await ThreadSafeUpdateAsync();
}
finally
{
semaphore.Release();
}
}
return Model;
}
}
and then I implement this class per request like this:
public class CachedStoreInfo : CachedModel<DesiredModel>
{
protected override async Task ThreadSafeUpdateAsync()
{
// make the trip to db and Blob service
Model = some result
}
protected override bool NeedsUpdate()
{
return someLogicToDecideIfNeedsUpdate;
}
}
finally, in the asp controller all what I need to do is this:
[HttpGet]
public async Task<DesiredModel> GetStoreInfo()
{
return await cachedStoreInfo.GetModel();
}
Is this a proper implementation ? and is this even necessary or there is a smarter way to achieve this? getting the time stamps from the blob was the main reason I though about caching the result
Your implementation looks correct. Of course the instance of CachedStoreInfo should be a singleton in a required scope (as I understand in your case it should be a singleton in scope of application).
can other threads enter here after the first one already updated the object?
As Kevin Gosse noted other threads can enter here. Your second check for NeedsUpdate() is a part of Double-checked locking pattern. And it might be a good optimization.
and is this even necessary or there is a smarter way to achieve this?
As for me your implementation is minimalist and smart enough
Background
I've created a working bot in C# but I'm failing to expand it to be a multi-tenant bot. I have created multiple bots in the Microsoft portal using this technique to identify themselves from the messaging endpoint:
https://example.com/api/messages/bot1
https://example.com/api/messages/bot2
https://example.com/api/messages/bot3
I can grab the LastSegment from the URL while in the MessagesController and store it in PrivateConversationData so I know which bot is talking in the current conversation. I intended use this stored 'bot id' in order to retrieve the Microsoft AppId & Password from the web.config (the bot's credentials are stored as a series of custom entries and not the standard appSettings as that only works for a single bot).
Credentials Problem
The authentication works well (nearly) as described here except when using async code with .ConfigureAwait(false) I can't get the HttpContext.Current as it becomes null when running on a different thread. This means I can't get the authenticated user's credentials either by looking them up in the web.config or by calling GetCredentialsFromClaims() since I've lost the authenticated user. If I use .ConfigureAwait(true) I just get deadlocks all over the place.
I have the credentials in the web.config but they are stored per bot and I need the 'bot id' from the URL above in order to get the credentials.
Question
The crux of the problem is: I need the URL to get the 'bot id' and I need the 'bot id' to get the credentials from the web.config but I can never reliably get access to the URL once I've passed a .ConfigureAwait(false) in the code. On the flip side, I can't get the 'bot id' from the PrivateConversationData since I need the bot's credentials in order to load it. A bit chicken and egg :-(
If anyone has any ideas of what I may be doing wrong or has an alternative approach to know which 'bot id' is currently executing I'd very much appreciate it.
Thanks
Please find below given the sample code.
public class StartUp {
public void Configuration(IAppBuilder app) {
var builder = new ContainerBuilder();
//Note: Initialize / register the Metadata Service that can bring the tenant details from the corresponding store
builder.RegisterType<TenantMetadataService>().As<ITenantMetadataService>();
//Note: This helps you in accessing the TenantMetadata from any constructor going forward after the below registry
builder.Register(ti => TenantMetadata.GetTenantMetadataFromRequest()).InstancePerRequest();
//TODO: Register the various services / controllers etc which may require the tenant details here
}
}
public class TenantMetadata {
public Guid TenantId { get;set; }
public Uri TenantUrl { get;set; }
public string TenantName { get;set; }
public static TenantMetadata GetTenantMetadataFromRequest() {
var context = HttpContext.Current;
//TODO: If you have any header like TenantId coming from the request, you can read and use it
var tenantIdFromRequestHeader = "";
//TODO: There will be a lazy cache that keeps building the data as new tenant's login or use the application
if(TenantCache.Contains(...))return TenantCache[Key];
//TODO: Do a look-up from the above step and then construct the metadata
var tenantMetadata = metadataSvc.GetTenantMetadata(...);
//TODO: If the data match does not happen from the Step2, build the cache and then return the value.
TenantCache.Add(key,tenantMetadata);
return tenantMetadata;
}
}
Note
The above code snippet uses the various service placeholders, cache and the other methods which will require to be used based on the designed application services. If you wish not to cache the tenant metadata, if it may contain some sensitive data, you can remove the caching implementation parts.
This implementation can be spread across all your web facing portals like your Web UI, Web Api and WebJobs etc so that it is same across all apps and it is easy to test and consume.
HTH.
what is the best way to save data in a state-machine-like application?
How the application works:
There are multiple states, like Loging, MainMenu, Registration, etc. There is a loop that working until the state reaches Exit.
while(currentState != States.Exit)
{
switch (currentState)
{
case Login:
// Do everything needed for the login, including showing the Login-Window.
LoginProcess();
break;
case MainMenu:
MainMenuProcess();
break;
// Etc...
}
}
The problem:
I want to save data in between these processes. For example I want to have a User Object after the login containing everything that has to do with the user. There are many variables I could have to save and they are not always initialized (i.e. the User can only exist after login).
How it's done until now:
Right now there are just "public" members that can be null if the respective process has not started. They are defined in the class of the State-Machine loop. This can get messy easily.
Expectations:
I would like to have a way to do this data-saving in a clean way. Maybe even extract it from the state-machine or something similar. Maybe there is a way to restrict processes to access members they should not change?
Thanks in advance.
You could persist it to a database, or serialize your model into a JSON object, this object could be saved, then loaded up later and deserialized into your domain model.
You can also consider sagas, there are frameworks that support the notion of this and might help solve the problem.
http://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf
Instead having every possible field for each process in the state machine, I would create small POCO objects which are in charge of passing information to each step of the state machine.
For example:
public class LoginProcessInfo
{
private readonly string username;
private readonly string password;
public LoginProcessInfo(string username, string password)
{
this.username = username;
this.password = password;
}
}
Now, with each iteration of the state, pass the relevant arguments to the method. You could either create a new one each time, or pool the created object if you're simply re-using them:
while(currentState != States.Exit)
{
switch (currentState)
{
case Login:
LoginProcess(new LoginProcessInfo(username, password));
break;
case MainMenu:
MainMenuProcess();
break;
}
}
Once authenticatated I use HttpContext.Current.User.Identity.Name; to ensure user is authorized to view a part of my site.
When I access certain parts of my site I need to get the User and get which context (organization they are logged into), url would be something like settings/supercompany/profile. where supercompany is the current context.
For each user I would need to check if they are admin in that company or a general user, if a general user then they cannot see certain things.
public class SettingsApi
{
private readonly string _userId;
private readonly string _contextId;
public SettingsApi(string userId, string contextId)
{
_userId = userId;
_contextId = contextId;
}
}
If I instantiate the class above from a controller (post or get), would caching somehow mess things up? Users role changed and I don't pick it up? Would something like the below work well?
var settings = new SettingsApi(HttpContext.Current.User.Identity.Name, currentContextId);
settings.IsAdmin();
Note: I would have used attributes to authorize but my requirements are I need to pick out the currentContext from the URL plus I need to use the class above elsewhere in my code.
Update
AuthorizeAttribute works well with caching, but the method used to authorize i.e.
protected override bool AuthorizeCore(HttpContextBase httpContext)
Will not hand me back an instance of the class I need...
Update 2 I don't want this class or an instance of this class to be cached in anyway, everytime I ask for a new instance I don't mind fetching one from the DB...
My Question - is the way I am coding ok? Will my user and his permissions NOT be cached?
It is possible, if you're not careful, to let MVC cache the output of the first request by an authenticated user. I use VaryByCustom and the current identity's name.
[OutputCache(VaryByCustom="user")]
public class SomeController : Controller
{
// etc.
}
In my Global.asax.cs I define:
public override string GetVaryByCustomString(HttpContext context, string custom)
{
if (custom.Equals("user", StringComparison.OrdinalIgnoreCase))
{
return context.User.Identity.IsAuthenticated ? context.User.Identity.Name : string.Empty;
}
return base.GetVaryByCustomString(context, custom);
}
If you are proposing to add instances of the SettingsApi to the cache then it definitely will not work as caching is app wide and so all users will end up sharing the same SettingsApi. Using the OutputCache should be fine (as long as you dont do something like put userid in a hidden field and use [OutputCache(VaryByCustom="user")] or similar).
If you are looking to cache the SettingsApi you should do so through SessionState which is per user/session and wont affect the authentication.