ToDo Bot | Part 10 | Coding the Delete Task Dialog | DELETE Data from Azure Cosmos DB | Microsoft Bot Framework

This is the bot series on ToDo Bot where we will be creating a Chatbot to create, view, and delete tasks. In this part, we will be coding the Delete Task Dialog.

The Azure Cosmos DB Emulator provides a local environment that emulates the Azure Cosmos DB service for development purposes. Using the Azure Cosmos DB Emulator, you can develop and test your application locally, without creating an Azure subscription or incurring any costs.

For more information, refer Microsoft Documentation.

Prerequisites

  1. Azure Cosmos DB Local Emulator
  2. Visual Studio
  3. Bot Emulator

Second and third requirement link is available on the Downloads page.

Video

Create Database and Container

We have already created the database and container in our last part. Refer to my post on Working with Azure Cosmos DB Local Emulator.

Authenticate the User

We have covered this as well by generating a new user id or authenticating the existing one. Refer to my post on Connecting Bot with Azure Cosmos DB.

View Tasks

We will be using the similar code which we have used while coding the View Task Dialog. First, we will show the list of all the tasks to the user and ask to choose anyone for delete.

Coding the Delete Task Dialog

Before starting with the Delete Task Dialog. Let us create a method in CosmosDBClient.cs to delete the record based on the user id and partition key.

public async Task<bool> DeleteTaskItemAsync(string partitionKey, string id)
        {
            var partitionKeyValue = partitionKey;
            var userId = id;

            try
            {
                // Delete an item. Note we must provide the partition key value and id of the item to delete
                ItemResponse<ToDoTask> todoTaskResponse = await this.container.DeleteItemAsync<ToDoTask>(userId, new PartitionKey(partitionKeyValue));
                Console.WriteLine("Deleted ToDoTask [{0},{1}]\n", partitionKeyValue, userId);
                return true;
            }
            catch(Exception ex)
            {
                Console.WriteLine(ex.Message);
                return false;              
            }
            
        }

Also, create another method for querying the DB to get the results in ascending order.

public async Task<List<ToDoTask>> QueryItemsAsync(string userId)
        {
            var sqlQueryText = $"SELECT * FROM c WHERE c.id = '{userId}' ORDER BY c._ts ASC";

            Console.WriteLine("Running query: {0}\n", sqlQueryText);

            QueryDefinition queryDefinition = new QueryDefinition(sqlQueryText);
            FeedIterator<ToDoTask> queryResultSetIterator = this.container.GetItemQueryIterator<ToDoTask>(queryDefinition);

            List<ToDoTask> todoTasks = new List<ToDoTask>();

            while (queryResultSetIterator.HasMoreResults)
            {
                FeedResponse<ToDoTask> currentResultSet = await queryResultSetIterator.ReadNextAsync();
                foreach (ToDoTask todoTask in currentResultSet)
                {
                    todoTasks.Add(todoTask);
                    Console.WriteLine("\tRead {0}\n", todoTask);
                }
            }
            return todoTasks;
        }

We want to pass the cosmos DB client object to the DeleteTaskDialog so that we can call the cosmos DB client methods.

Modify the Initialization of DeleteTaskDialog in MainDialog constructor.

AddDialog(new DeleteTaskDialog(_cosmosDBClient));

Create a property at the class level of the DeleteTaskDialog and initialize them in the constructor.

private readonly CosmosDBClient _cosmosDBClient;
        public DeleteTaskDialog(CosmosDBClient cosmosDBClient) : base(nameof(DeleteTaskDialog))
        {
            _cosmosDBClient = cosmosDBClient;

            var waterfallSteps = new WaterfallStep[]
            {
                ShowTasksStepAsync,
                DeleteTasksStepAsync,
                DeleteMoreTasksStepAsync,
            };

            AddDialog(new WaterfallDialog(nameof(WaterfallDialog), waterfallSteps));
            AddDialog(new TextPrompt(nameof(TextPrompt)));
            AddDialog(new ConfirmPrompt(nameof(ConfirmPrompt)));

            InitialDialogId = nameof(WaterfallDialog);
        }

We have created following 3 steps –

  1. ShowTasksStepAsync – Show the tasks in an adaptive card for the user to select and delete.
  2. DeleteTasksStepAsync – Delete the task and ask the user if he/she wants to delete more tasks if any.
  3. DeleteMoreTasksStepAsync – Rerun the DeleteTaskDialog if the user wants to delete more tasks else end the dialog.

Add the below code in all the 3 steps.

private async Task<DialogTurnResult> ShowTasksStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            List<ToDoTask> toDoTasks = await _cosmosDBClient.QueryItemsAsync(User.UserID);

            if (toDoTasks.Count == 0)
            {
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("You don't have any tasks to delete."), cancellationToken);
                return await stepContext.EndDialogAsync(null, cancellationToken);
            }

            List<string> taskList = new List<string>();
            for (int i = 0; i < toDoTasks.Count; i++)
            {
                taskList.Add(toDoTasks[i].Task);
            }

            await stepContext.Context.SendActivityAsync(MessageFactory.Text("Please select the tasks you want to delete."), cancellationToken);

            var card = new AdaptiveCard(new AdaptiveSchemaVersion(1, 0))
            {
                // Use LINQ to turn the choices into submit actions
                Actions = taskList.Select(choice => new AdaptiveSubmitAction
                {
                    Title = choice,
                    Data = choice,  // This will be a string
                }).ToList<AdaptiveAction>(),
            };
            // Prompt
            return await stepContext.PromptAsync(nameof(ChoicePrompt), new PromptOptions
            {
                Prompt = (Activity)MessageFactory.Attachment(new Attachment
                {
                    ContentType = AdaptiveCard.ContentType,
                    // Convert the AdaptiveCard to a JObject
                    Content = JObject.FromObject(card),
                }),
                Choices = ChoiceFactory.ToChoices(taskList),
                // Don't render the choices outside the card
                Style = ListStyle.None,
            },
                cancellationToken);
        }

        private async Task<DialogTurnResult> DeleteTasksStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            stepContext.Values["TaskToDelete"] = ((FoundChoice)stepContext.Result).Value;
            string taskToDelete = (string)stepContext.Values["TaskToDelete"];
            bool deleteTask = await _cosmosDBClient.DeleteTaskItemAsync(taskToDelete, User.UserID);

            if (deleteTask)
            {
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("Task '" + taskToDelete + "' successfully deleted."), cancellationToken);

                List<ToDoTask> toDoTasks = await _cosmosDBClient.QueryItemsAsync(User.UserID);

                if (toDoTasks.Count == 0)
                {
                    await stepContext.Context.SendActivityAsync(MessageFactory.Text("No Tasks left. All your tasks are deleted."), cancellationToken);
                    return await stepContext.EndDialogAsync(null, cancellationToken);
                }

                return await stepContext.PromptAsync(nameof(ConfirmPrompt), new PromptOptions
                {
                    Prompt = MessageFactory.Text("Would you like to Delete more tasks?")
                }, cancellationToken);
            }
            else
            {
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("Task '" + taskToDelete + "' could not be deleted. Either it has been already deleted or some error occurred."), cancellationToken);
                return await stepContext.EndDialogAsync(null, cancellationToken);
            }

        }

        private async Task<DialogTurnResult> DeleteMoreTasksStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
        {
            if ((bool)stepContext.Result)
            {
                return await stepContext.ReplaceDialogAsync(InitialDialogId, null, cancellationToken);
            }
            else
            {
                await stepContext.Context.SendActivityAsync(MessageFactory.Text("Ok."));
                return await stepContext.EndDialogAsync(null, cancellationToken);
            }
        }

Run the project and test the bot in the emulator.

Check the Azure Cosmos DB for the records deleted by the bot.

Thank you All!!! Hope you find this useful.


One thought on “ToDo Bot | Part 10 | Coding the Delete Task Dialog | DELETE Data from Azure Cosmos DB | Microsoft Bot Framework

Add yours

Up ↑

%d bloggers like this: