I wrote a piece of code in c# and I have a warning that I can't avoid and don't understand. first my code :
private async Task<ActionResult<List<string>>> UpdateImagesInBlob(List<BlobImage>? origin, List<BlobImage>? goal)
{
if (origin is null && goal is null)
return new BadRequestObjectResult("Update BlobImage Error");
if (goal is null)
{
var deleteOnlyResult = await RemoveImagesInBlob(origin!);
return deleteOnlyResult;
}
else if (origin is null)
{
var addOnlyResult = await AddImagesInBlob(goal);
return addOnlyResult;
}
var toDelete = origin.Where(im => !goal.Contains(im)).ToList();
var toAdd = goal.Where(im => !origin.Contains(im)).ToList();
IActionResult addResult, deleteResult;
addResult = await AddImagesInBlob(toAdd);
deleteResult = await RemoveImagesInBlob(toDelete);
if (addResult is OkObjectResult && deleteResult is OkObjectResult)
return new OkObjectResult(addResult);
else
return new BadRequestObjectResult("Update BlobImage Error");
}
(the code is not finished)
I use it like this :
var imageUpdateResult = await UpdateImagesInBlob(current.Images, new.Images);
if (imageUpdateResult is OkObjectResult)
I have a compilation warning Compiler Warning (level 1) CS0184
where is my issue ? my code return okObjectResult on success (addimageinblob and removeimageinblob return okObjectResult)
Is their something I missed ?
Related
In ASP.NET Core-6 Web API using Entity Framework, I have this code:
public async Task<Response<NotificationCredentialListDto>> CreateNotificationCredentialAsync(CreateNotificationCredentialDto requestDto)
{
var userName = _currentUserService.UserName;
var response = new Response<NotificationCredentialListDto>();
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
if (userName != null)
{
try
{
var notification = _mapper.Map<NotificationCredential>(requestDto);
var existingNotification = _dbContext.NotificationCredentials.LastOrDefault(e => e.MerchantId == notification.MerchantId && e.IsModified == false);
if (existingNotification != null)
{
existingNotification.IsModified = true;
_unitOfWork.MerchantNotificationCredentials.Update(notification);
await _unitOfWork.Save();
}
requestDto.IsModified = false;
notification.IsModified = requestDto.IsModified;
notification.UserName = requestDto.UserName;
requestDto.BasicAuth = encoded;
notification.BasicAuth = requestDto.BasicAuth;
notification.CreatedBy = _currentUserService.UserName;
await _unitOfWork.MerchantNotificationCredentials.InsertAsync(notification);
await _unitOfWork.Save();
return response;
}
catch (Exception ex)
{
_logger.Error("An error occured: " + ex);
transaction.Dispose();
return response;
}
}
_logger.Error("Registration failed");
transaction.Dispose();
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.Successful = false;
response.Message = "Registration failed. Please try again";
return response;
}
}
What I want to achieve is that if record exist, it should update the last record as:
IsModified = true
and also insert a new one.
But I git this error:
An error occured: System.InvalidOperationException: Queries performing 'LastOrDefault' operation must have a deterministic sort order. Rewrite the query to apply an 'OrderBy' operation on the sequence before calling 'LastOrDefault'.
How do I resolve this?
Thanks
First, I think your code does not work correctly because after checking the condition existingNotification != null and updating the entity MerchantNotificationCredentials you are inserting it in next lines.
Second, I suggest change code to this:
public async Task<Response<NotificationCredentialListDto>> CreateNotificationCredentialAsync(CreateNotificationCredentialDto requestDto)
{
var userName = _currentUserService.UserName;
var response = new Response<NotificationCredentialListDto>();
using (var transaction = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
if (userName != null)
{
try
{
var notification = _mapper.Map<NotificationCredential>(requestDto);
var existingNotification = await _dbContext.NotificationCredentials.Where(e => e.MerchantId == notification.MerchantId && e.IsModified == false).OrderByDescending(x=>x.CreatedAt).FirstOrDefaultAsync();
if (existingNotification != null)
{
existingNotification.IsModified = true;
_unitOfWork.MerchantNotificationCredentials.Update(notification);
await _unitOfWork.Save();
return response;
}
requestDto.IsModified = false;
notification.IsModified = requestDto.IsModified;
notification.UserName = requestDto.UserName;
requestDto.BasicAuth = encoded;
notification.BasicAuth = requestDto.BasicAuth;
notification.CreatedBy = _currentUserService.UserName;
await _unitOfWork.MerchantNotificationCredentials.InsertAsync(notification);
await _unitOfWork.Save();
return response;
}
catch (Exception ex)
{
_logger.Error("An error occured: " + ex);
transaction.Dispose();
return response;
}
}
_logger.Error("Registration failed");
transaction.Dispose();
response.StatusCode = (int)HttpStatusCode.BadRequest;
response.Successful = false;
response.Message = "Registration failed. Please try again";
return response;
}
}
Like #Rafalon mentioned, you need to sort the collection using OrderBy() before using LastOrDefault().
var existingNotification = _dbContext.NotificationCredentials.OrderBy(e => e.CreatedAt).LastOrDefault(e => e.MerchantId == notification.MerchantId && e.IsModified == false);
But be careful because multiple records could have been added before CreateNotificationCredentialAsync is called. Maybe you should search for all records where MerchantId = notification.MerchantId and e.IsModified = false:
var existingNotifications = _dbContext.NotificationCredentials.Where(e => e.MerchantId == notification.MerchantId && e.IsModified == false).ToList();
and update IsModified property to all of them.
I've got a legacy service that I need to emulate that uses Soap as the message format. Unfortunately, due to various circumstances, I'm stuck using asmx and .Net Framework (due to it's Soap handling capabilities)
My problem is that I now need to call a certain API from within the service, and having used NSWAG to auto generate the client api, I'm finding that when I try and call it, it's giving a 500 error. This isn't necessairly a problem, as I'm sort of expecting that at the moment.
The issue is that when it throws the auto generated ApiException message as part of the 500 processing, it's jumping to the 'Finally' section for the response and the client, and then just throws an object reference exception...somewhere.
There's very little detail at all in the Exception, with the only bit of interest (I think) being that it says "This exception was originally thrown at this call stack:
System.Web.ThreadContext.AssociateWithCurrentThread(bool)"
I'm sort of lost on where to start with this one, but I'm hoping it's a common enough problem that there's a solution out there somewhere!
[Edit - updated to include code]
public System.Threading.Tasks.Task<SomeDto> RetrieveAsync(string someIdentifier, string includePermissibleActions)
{
return RetrieveAsync(someIdentifier, includePermissibleActions, System.Threading.CancellationToken.None);
}
public async System.Threading.Tasks.Task<SomeDto> RetrieveAsync(string someIdentifier, string includePermissibleActions, System.Threading.CancellationToken cancellationToken)
{
if (someIdentifier == null)
throw new System.ArgumentNullException("someIdentifier");
var urlBuilder_ = new System.Text.StringBuilder();
urlBuilder_.Append(BaseUrl != null ? BaseUrl.TrimEnd('/') : "").Append("/api/v1/someName/{someIdentifier}?");
urlBuilder_.Replace("{someIdentifier}", System.Uri.EscapeDataString(ConvertToString(someIdentifier, System.Globalization.CultureInfo.InvariantCulture)));
if (includePermissibleActions != null)
{
urlBuilder_.Append(System.Uri.EscapeDataString("includePermissibleActions") + "=").Append(System.Uri.EscapeDataString(ConvertToString(includePermissibleActions, System.Globalization.CultureInfo.InvariantCulture))).Append("&");
}
urlBuilder_.Length--;
var client_ = _httpClient;
var disposeClient_ = false;
try
{
using (var request_ = new System.Net.Http.HttpRequestMessage())
{
request_.Method = new System.Net.Http.HttpMethod("GET");
PrepareRequest(client_, request_, urlBuilder_);
var url_ = urlBuilder_.ToString();
request_.RequestUri = new System.Uri(url_, System.UriKind.RelativeOrAbsolute);
PrepareRequest(client_, request_, url_);
var response_ = await client_.SendAsync(request_, System.Net.Http.HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
var disposeResponse_ = true;
try
{
var headers_ = System.Linq.Enumerable.ToDictionary(response_.Headers, h_ => h_.Key, h_ => h_.Value);
if (response_.Content != null && response_.Content.Headers != null)
{
foreach (var item_ in response_.Content.Headers)
headers_[item_.Key] = item_.Value;
}
ProcessResponse(client_, response_);
var status_ = (int)response_.StatusCode;
if (status_ == 200)
{
var objectResponse_ = await ReadObjectResponseAsync<SomeDto>(response_, headers_).ConfigureAwait(false);
if (objectResponse_.Object == null)
{
throw new ApiException("Response was null which was not expected.", status_, objectResponse_.Text, headers_, null);
}
return objectResponse_.Object;
}
else
if (status_ == 401)
{
string responseText_ = (response_.Content == null) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("Unauthorized", status_, responseText_, headers_, null);
}
else
if (status_ == 403)
{
string responseText_ = (response_.Content == null) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("Forbidden", status_, responseText_, headers_, null);
}
else
if (status_ == 404)
{
string responseText_ = (response_.Content == null) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("NotFound", status_, responseText_, headers_, null);
}
else
if (status_ == 500)
{
string responseText_ = (response_.Content == null) ? string.Empty : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("InternalServerError", status_, responseText_, headers_, null);
}
else
{
var responseData_ = response_.Content == null ? null : await response_.Content.ReadAsStringAsync().ConfigureAwait(false);
throw new ApiException("The HTTP status code of the response was not expected (" + status_ + ").", status_, responseData_, headers_, null);
}
}
finally
{
if (disposeResponse_)
response_.Dispose();
}
}
}
finally
{
if (disposeClient_)
client_.Dispose();
}
}
I don't know the async/await mechanism very well and I searched on Google, but without finding an acceptable answer.
I have a question about the code below:
When using .Result, the switch() block is executed.
// Here the flow starts - the callee is a VueJS axios ajax call
[Route("ChangeStatusOrders/ValidateStatus")]
[HttpPost]
public async Task<ActionResult> ValidateStatus(OrdersToBeProcessed model)
{
bool success = false;
string message = "";
try
{
model.ReturnData = await _statusBusiness.Validate(model);
success = true;
return Json(new { Success = true, ReturnData = model.ReturnData }, JsonRequestBehavior.AllowGet);
}
catch (ArgumentException ex)
{
message = $"Bad Request {ex.Message}";
_log.Error(message);
Response.StatusCode = 400;
return Json(new { Success = false, ErrorMessage = message }, JsonRequestBehavior.AllowGet);
}
catch (Exception ex)
{
message = $"Spomething wrong message here - {ex.Message}";
_log.Error(message);
Response.StatusCode = 500;
return Json(new { Success = false, ErrorMessage = message }, JsonRequestBehavior.AllowGet);
}
finally
{
var param = new
{
model.Orders,
model.NotesText,
model.CompanyID,
model.CompanyName,
model.NewStatus,
model.ReturnData,
model.CodeType,
model.UserID,
model.isInternalUser,
Message = message
};
LogUserAction(ActionType.ChangeStatusOrders, success, param);
}
}
private readonly IDictionary<bool, Func<List<string>, int, List<Order>>> _dic;
constructor()
{
// below methods are not async
_dic = new Dictionary<bool, Func<List<string>, int, List<Pedido>>>
{
{ true, (p) => _orderRepository.GetByID(p) },
{ false, (p) => _orderRepository.GetByCompanyID(p) }
};
}
public async Task<ProcessResults> Validate(OrdersToBeProcessed model)
{
ProcessResults result = new ProcessResults();
bool useOurID = (model.CodeType == 1);
return await Task.Run(() =>
{
try
{
List<string> ordersList = SplitOrders(model.Orders);
List<string> buffer = ordersList.Distinct().ToList();
List<Order> ordersList = new List<Order>(buffer.Count());
buffer.ForEach(item =>
{
var tmp = new Order();
if (useOurID)
tmp.ID = Convert.ToInt64(item);
else
tmp.CompanyId = item;
orderList.Add(tmp);
});
List<Order> foundOrders = _dic[useOurId](buffer);
result.SuccessfullOrders = new List<ProcessedOrder>();
result.NotFoundOrders = new List<ProcessedOrder>();
result.NotAllowedOrders = new List<ProcessedOrder>();
// merge the list of order's id with those one's found in database
List<Orders> union = foundOrders.MergeAndReplace(ordersList).ToList();
foreach (var item in union)
{
ProcessedOrder ret = ValidateAsync(item).Result; // <-- here is an Async call
switch (ret.ProcessedResult)
{
case ProcessedResultEnum.CanBeProcessed:
result.SuccessfullOrders.Add(ret);
break;
case ProcessedResultEnum.OrderNotFound:
result.NotFoundOrders.Add(ret);
break;
case ProcessedResultEnum.CannotBeProcessed:
result.NotAllowedOrders.Add(ret);
break;
}
}
return result;
});
private async Task<Order> ValidateAsync(Order item)
{
ProcessedOrder ret = new ProcessedOrder();
ret.CompanyID = item.CompanyID;
ret.Name = item.Name;
ret.ID = item.ID;
ret.Status = (item.Status?.ID).ToString();
var queueOrderItems = await _queueOrderService.SearchByOrderIdAsync(item.ID);
if (item.ID == 0 || String.IsNullOrEmpty(iitem.CodigoEntidade))
{
ret.Message = "Order not found in database.";
ret.Result = ProcessedResultEnum.OrderNotFound;
}
else if (item.Status == null || queueOrderItems == null)
{
ret.Message = "Order Cannot Be Processed";
ret.Result = ProcessedResultEnum.CannotBeProcessed;
}
else
{
ret.Message = "Order Can Be Processed";
ret.Result = ProcessedResultEnum.CanBeProcessed;
}
return ret;
}
public async Task<SearchBiIdResult> SearchByOrderIdAsync(long orderID )
{
SearchByIDResult ret;
SearchByIDRequest request = new SearchByIDRequest() { OrderID = orderID };
// calls a WCF async method descibed below
SearchByIDResponse response = await _queueOrderItemsClient.SearchByIDAsync(request);
ret = _mapper.Map<SearchByIDResponse>(response);
return ret;
}
// WCF async method definition (in reference.cs)
public System.Threading.Tasks.Task<UI.Services.Business.QueuesService.SearchByIDResponse> SearchByIDAsync(UI.Services.Business.QueuesService.SearchByIDRequest request) {
return base.Channel.SearchByIDAsync(request);
}
However, if I replace .Result with
ProcessedOrder ret = await ValidateAsync(item);
the loop immediately returns back up to the foreach statement, without executing the switch() block.
Can someone explain to me why this behavior?
I changed
return await Task.Run(() =>
to
return await Task.Run(async () =>
and the foreach block behaved as expected.
I need to add a question 'Did this help?' after getting the response from QnA and take the feedback from user. If there is no response for this and if the next input is a completely new query, the flow should restart from bot.cs
I tried using a textprompt, but when tested in emulator, bot doesn't wait for user input after the prompt.
Bot.cs
public async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default(CancellationToken))
{
var activity = turnContext.Activity;
var dc = await _dialogs.CreateContextAsync(turnContext);
if (turnContext == null)
{
throw new ArgumentNullException(nameof(turnContext));
}
if (turnContext.Activity.Type == ActivityTypes.Message)
{
if (turnContext.Activity.Text != null)
{
var luisResults = await _services.LuisServices[LuisConfiguration].RecognizeAsync(dc.Context, cancellationToken);
var luisProperties = LuisEntities.FromLuisResults(luisResults);
await _luisEntitiesAccessor.SetAsync(turnContext, luisProperties);
var topScoringIntent = luisResults?.GetTopScoringIntent();
var topIntent = topScoringIntent.Value.intent;
switch (topIntent)
{
case NoneIntent:
await dc.BeginDialogAsync(QnADialog.Name);
break;
case GreetingsIntent:
await dc.BeginDialogAsync(QnAGreetingsDialog.Name);
break;
case CredentialsIntent:
await dc.BeginDialogAsync(CredentialsDialog.Name);
break;
case ContactusIntent:
await dc.BeginDialogAsync(FeedbackDialog.Name);
break;
case FeedbackIntent:
await dc.BeginDialogAsync(FeedbackDialog.Name);
break;
default:
await dc.Context.SendActivityAsync("I didn't understand what you just said to me.");
break;
}
}
else if (string.IsNullOrEmpty(turnContext.Activity.Text))
{
await HandleSubmitActionAsync(turnContext, userProfile);
}
}
else if (turnContext.Activity.Type == ActivityTypes.ConversationUpdate)
{
if (turnContext.Activity.MembersAdded != null)
{
await SendWelcomeMessageAsync(turnContext);
}
}
else if (turnContext.Activity.Type == ActivityTypes.Event)
{
await SendWelcomeMessageAsync(turnContext);
}
else
{
await turnContext.SendActivityAsync($"{turnContext.Activity.Type} event detected");
}
// Save the dialog state into the conversation state.
await ConversationState.SaveChangesAsync(turnContext, false, cancellationToken);
}
QnADialog.cs - dialog in which I want the prompt to work
public class QnADialog : ComponentDialog
{
public const int QnaNumResults = 1;
public const double QnaConfidenceThreshold = 0.5;
public const string QnaConfiguration = "QnAFaqSubscriptionKey";
private const string QnAFeedbackDialog = "qnaDialog";
public const string Name = "QnA";
public const string TextPrompt = "textPrompt";
private readonly BotServices _services;
private readonly IStatePropertyAccessor<UserProfile> _userProfileAccessor;
Action<string, string, bool, int, int> updateQna;
private int InvalidMessageCount = 0;
string Query = string.Empty;
List<int> qnaIdStorage;
UserProfile userProfile = new UserProfile();
public QnADialog(Action<string, string, bool, int, int> updateQna, bool isCollection, List<int> rotationTemStorage, BotServices services, UserProfile _userProfile, IStatePropertyAccessor<UserProfile> userProfileAccessor, int invalidMessageCount = 0, string dialogId = null)
: base(Name)
{
_services = services ?? throw new ArgumentNullException(nameof(services));
_userProfileAccessor = userProfileAccessor ?? throw new ArgumentNullException(nameof(userProfileAccessor));
userProfile = _userProfile;
this.updateQna = updateQna;
this.InvalidMessageCount = invalidMessageCount;
qnaIdStorage = rotationTemStorage;
var waterfallSteps = new WaterfallStep[]
{
BeginStepAsync,
FetchFAQResultStepAsync,
FeedbackStepAsync,
FeedbackResponseStepAsync,
};
AddDialog(new WaterfallDialog(QnAFeedbackDialog, waterfallSteps));
AddDialog(new TextPrompt("userFeed"));
}
public async Task<DialogTurnResult> BeginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken = default(CancellationToken))
{
var messageToForward = stepContext.Context.Activity;
UserProfile.previousQuestion = messageToForward.Text;
string[] supportList = { "HELP", "FEEDBACK", "SUPPORT", "ESCALATE", "AGENT" };
if (messageToForward.Text == null || messageToForward.Text.ToLower() == "no")
{
await stepContext.Context.SendActivityAsync("Sorry, I was not able to help you.");
return await stepContext.EndDialogAsync();
}
else if (messageToForward.Text == null || supportList.Any(x => x == messageToForward.Text.ToUpper()))
{
await stepContext.Context.SendActivityAsync("Please reach out to... ");
return await stepContext.EndDialogAsync();
}
else
{
return await stepContext.NextAsync();
}
}
private async Task<DialogTurnResult> FetchFAQResultStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var message = stepContext.Context.Activity;
var qnaResult = await FaqQnaMakerService.GetQnaResult(_services, stepContext, this.Query);
var qnaIdColl = GetQnaIdColl(this.Query, qnaResult);
int qnaPreviousId = 0;
int qnaNewId = 0;
if (qnaIdColl != null && qnaIdColl.Count > 1)
{
qnaIdColl = qnaIdColl.Distinct().OrderBy(x => x).ToList();
//Compare the previous Qnaid collection and existing collection , if it is matching produce the result.
var matchItem = qnaIdColl.Intersect(qnaIdStorage);
if (matchItem.Count() == 0)
{
//If there is no previous collection Qna id then take the first item from the existing Qna collection
qnaNewId = qnaIdColl.FirstOrDefault();
}
else
{
//If there any previous Qnaid that contain in the existing collection then pick the next value and generate a new qna result.
qnaPreviousId = matchItem.FirstOrDefault();
qnaNewId = GetNextRotationKey(qnaIdColl, qnaPreviousId);
}
//Create a new response based on selected new qna id.
qnaResult = new[] { qnaResult.Where(x => x.Id == qnaNewId).Single() };
}
if (qnaResult.First().Answer.Length > 0)
{
if (qnaResult.First().Score > 0)
{
updateQna(this.Query, qnaResult.First().Answer, false, qnaPreviousId, qnaNewId);
InvalidMessageCount = 0;
var QuestionCollection = TextFormatter.FormattedQuestionColl(qnaResult.First().Answer);
if (QuestionCollection != null)
{
userProfile.IsAswerCollection = true;
updateQna(this.Query, qnaResult.First().Answer, true, qnaPreviousId, qnaNewId);
var replyMessage = stepContext.Context.Activity.CreateReply();
replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.QnaAttachment(new Tuple<string, string[]>(QuestionCollection.Item2, QuestionCollection.Item3)) };
if (!string.IsNullOrEmpty(QuestionCollection.Item1))
{
await stepContext.Context.SendActivityAsync(QuestionCollection.Item1);
}
await stepContext.Context.SendActivityAsync(replyMessage);
return await stepContext.EndDialogAsync();
}
else
{
await stepContext.Context.SendActivityAsync(qnaResult.First().Answer);
}
}
else
{
InvalidMessageCount++;
return await stepContext.ContinueDialogAsync();
}
}
return await stepContext.NextAsync();
}
private async Task<DialogTurnResult> FeedbackStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
return await stepContext.PromptAsync("userFeed", new PromptOptions
{
Prompt = stepContext.Context.Activity.CreateReply("Did this help?")
});
}
private async Task<DialogTurnResult> FeedbackResponseStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)
{
var message = stepContext.Context.Activity;
var mesgActivity = message as Activity;
string var = userProfile.qnaData;
var qnaResultModel = new { InvalidMessageCount = 0, originalQueryText = string.Empty };
NeedMoreInformation needmoreInfo = NeedMoreInformation.NotSelected;
if (message != null && message.Text == null && message.Value != null)
{
dynamic value = mesgActivity.Value.ToString();
UserReply response = JsonConvert.DeserializeObject<UserReply>(value);
if (!string.IsNullOrEmpty(response.Reply))
{
mesgActivity.Text = response.Reply;
}
}
//This if condition work only the user reply back to the question "Did this help?"
if (userProfile.needMoreInformation == true && message?.Text?.ToLower() != "yes" && message?.Text?.ToLower() != "no")
{
//The response message pass to LUIS service to understand the intention of the conversation is “yes” or “no”
bool? moreInformationYes = await LUISService.GetResultAESChatBotYesNo(message?.Text);
if (moreInformationYes != null && moreInformationYes == true)
{
//Once the LUIS understand the conversation change the original message to yes.
message.Text = "yes";
//needmoreInfo = NeedMoreInformation.Yes;
}
else if (moreInformationYes != null && moreInformationYes == false)
{
////Once the LUIS understand the conversation change the original message to no.
message.Text = "no";
needmoreInfo = NeedMoreInformation.No;
}
else
{
needmoreInfo = NeedMoreInformation.None;
}
}
if (userProfile.needMoreInformation == true && message?.Text?.ToLower() == "yes")
{
userProfile.qnaInvalidMessageCount = 0;
userProfile.needMoreInformation = false;
dynamic value = stepContext.Context.Activity.Value;
var output = JsonConvert.DeserializeObject<UserReply>(stepContext.Context.Activity.Value.ToString());
if (userProfile.feedbackCard == false)
{
var replyMessage = stepContext.Context.Activity.CreateReply();
replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.FeedbackAdapativecard() };
await stepContext.Context.SendActivityAsync(replyMessage);
}
if (output.Reply != "yes")
{
await AdaptiveCardReplyAsync(_services, stepContext, userProfile);
}
}
else if (userProfile.needMoreInformation == true && message?.Text?.ToLower() == "no")
{
userProfile.qnaInvalidMessageCount = 0;
userProfile.needMoreInformation = false;
dynamic value = stepContext.Context.Activity.Value;
if (value.Type == "GetMoreContent")
{
await AdaptiveCardGetMoreContent(_services, stepContext, userProfile);
}
else if (value.Type == "GetHelpSubmit")
{
await AdaptiveCardReplyAsync(_services, stepContext, userProfile);
}
else if (userProfile.getMoreContentCard == false)
{
var replyMessage = stepContext.Context.Activity.CreateReply();
replyMessage.Attachments = new List<Attachment>() { AllAdaptiveCard.GetMoreContent() };
await stepContext.Context.SendActivityAsync(replyMessage);
}
// context.Wait(AdaptiveCardGetMoreContent);
}
else
{
await stepContext.BeginDialogAsync(nameof(Bot.cs));
}
return await stepContext.EndDialogAsync();
}
}
After this prompt it should go to the next step as added in the waterfall steps but it does not. Any possible suggestions/help would be greatly appreciated. Thanks in advance!
Without seeing the code for your other steps inside the Waterfall such as BeginStepAsync and FetchFAQResultStepAsync it is difficult to give you an exact answer for your scenario.
How I would suggest you accomplish this is through the use of a message with suggested actions underneath this message, once either of this actions is clicked both options will disappear, thus removing the potential for multiple submissions by the same user for the same answer reply.
You have a couple of options here:
1) Use this dated sample that uses v3.9.0 of the Microsoft.Bot.Builder NuGet package, the meat of which is in the QnADialog and FeedbackDialog classes.
The important part is that the QnADialog implements QnAMakerDialog.
2) Right after where you send the reply to the user with the answer (inside FetchFAQResultsStepAsync I assume) you could add the following code:
var feedback = ((Activity)context.Activity).CreateReply("Did you find what you need?");
feedback.SuggestedActions = new SuggestedActions()
{
Actions = new List<CardAction>()
{
new CardAction(){ Title = "Yes", Type=ActionTypes.PostBack, Value=$"yes-positive-feedback" },
new CardAction(){ Title = "No", Type=ActionTypes.PostBack, Value=$"no-negative-feedback" }
}
};
await context.PostAsync(feedback);
EDIT
Thank you for providing the full code for your QnADialog class, unfortunately I cannot run it locally because the implementations for methods such as GetQnaIdColl, GetNextRotationKey, TextFormatter.FormattedQuestionColl among other methods and classes that you call but haven't provided. Your code for prompting a user for a response looks right but it sounds like you're not even getting the feedback prompt to show, or you're getting the feedback prompt to show but you get stuck on there - can you confirm which it is? Have you tried stepping through the code to see which path it takes?
Another suggestion would be to separate out your QnA step and Feedback steps into separate Dialogs, I have proved and example feedback dialog below.
using Microsoft.Bot.Builder;
using Microsoft.Bot.Builder.Dialogs;
using System.Threading;
using System.Threading.Tasks;
namespace ChatBot.VirtualAssistant.Dialogs
{
public class RateAnswerDialog : ComponentDialog
{
public RateAnswerDialog()
: base(nameof(RateAnswerDialog))
{
InitialDialogId = nameof(RateAnswerDialog);
var askToRate = new WaterfallStep[]
{
AskRating,
FinishDialog
};
AddDialog(new TextPrompt(nameof(TextPrompt)));
AddDialog(new WaterfallDialog(InitialDialogId, askToRate));
}
private async Task<DialogTurnResult> AskRating(WaterfallStepContext sc, CancellationToken cancellationToken)
{
PromptOptions promptOptions = new PromptOptions
{
Prompt = MessageFactory.Text("Was this helpful?")
};
return await sc.PromptAsync(nameof(TextPrompt), promptOptions);
}
private async Task<DialogTurnResult> FinishDialog(WaterfallStepContext sc, CancellationToken cancellationToken)
{
return await sc.EndDialogAsync(sc);
}
protected override async Task<DialogTurnResult> EndComponentAsync(DialogContext outerDc, object context, CancellationToken cancellationToken)
{
var waterfallContext = (WaterfallStepContext)context;
var userResponse = ((string)waterfallContext.Result).ToLowerInvariant();
if (userResponse == "yes")
{
await waterfallContext.Context.SendActivityAsync("Thank you for your feedback");
}
else if (userResponse == "no")
{
await waterfallContext.Context.SendActivityAsync("Sorry I couldn't help you");
}
else
{
await waterfallContext.Context.SendActivityAsync("The valid answers are 'yes' or 'no'");
// TODO reprompt if required
}
return await outerDc.EndDialogAsync();
}
}
}
I have this function that updates a CouchDB Database but I want it to try updating again if the Response Code is conflict, i want it to have 3 tries, how do I do that?
public async Task<HttpResponseMessage> UpdateRecord(Profile latestProfile)
{
ProfileRecordByUpn profileRecord = await this.GetProfileByUpn(latestProfile);
Profile oldProfile = profileRecord.Rows.First().Value;
var client = this.clientFactory.CreateClient(NamedHttpClients.COUCHDB);
var formatter = new JsonMediaTypeFormatter();
formatter.SerializerSettings = new JsonSerializerSettings
{
Formatting = Formatting.Indented,
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
var query = HttpUtility.ParseQueryString(string.Empty);
query["rev"] = oldProfile.Rev;
//Setting the profile Active = true, because as of now we don't have any UI for disabling the account
latestProfile.Active = oldProfile.Active;
DateTimeOffset now = DateTimeOffset.Now;
latestProfile.Created = oldProfile.Created;
latestProfile.Modified = now;
//This will check if we the InApp boolean value changed then will set date to Enabled/Disabled
if (oldProfile.InApp != latestProfile.InApp)
{
if (latestProfile.InApp == true)
{
latestProfile.InAppEnabled = now;
latestProfile.InAppDisabled = oldProfile.InAppDisabled;
}
else
{
latestProfile.InAppDisabled = now;
latestProfile.InAppEnabled = oldProfile.InAppEnabled;
}
}
else
{
latestProfile.InAppEnabled = oldProfile.InAppEnabled;
latestProfile.InAppDisabled = oldProfile.InAppDisabled;
}
//This will check if we the SMS boolean value changed then will set date to Enabled/Disabled
if (oldProfile.SMS != latestProfile.SMS)
{
if (latestProfile.SMS == true)
{
latestProfile.SMSEnabled = now;
latestProfile.SMSDisabled = oldProfile.SMSDisabled;
}
else
{
latestProfile.SMSDisabled = now;
latestProfile.SMSEnabled = oldProfile.SMSEnabled;
}
}
else
{
latestProfile.SMSEnabled = oldProfile.SMSEnabled;
latestProfile.SMSDisabled = oldProfile.SMSDisabled;
}
//This will check if we the SMS boolean value changed then will set date to Enabled/Disabled
if (oldProfile.Email != latestProfile.Email)
{
if (latestProfile.Email == true)
{
latestProfile.EmailEnabled = now;
latestProfile.EmailDisabled = oldProfile.EmailDisabled;
}
else
{
latestProfile.EmailDisabled = now;
latestProfile.EmailEnabled = oldProfile.EmailEnabled;
}
}
else
{
latestProfile.EmailEnabled = oldProfile.EmailEnabled;
latestProfile.EmailDisabled = oldProfile.EmailDisabled;
}
var response = await this.couchDbClient.AuthenticatedQuery(async (c) => {
return await c.PutAsync($"{API_PROFILES_DB}/{oldProfile.Id.ToString()}?{query}", latestProfile, formatter);
}, NamedHttpClients.COUCHDB, client);
return response;
}
so I will be calling this function from another function? Do I make an another function which is a higher order function and pass this function as a parameter to that higher order function?
Higher-order functions in C# are implemented by methods taking delegates as parameters, usually an Action or Func delegate.
In this case, you should use an established library like Polly.
var policy = Policy
.HandleResult<HttpResponseMessage>(r => r.StatusCode == HttpStatusCode.Conflict)
.RetryAsync(3);
var result = await policy.ExecuteAsync(() => UpdateRecord(latestProfile));
Update to do it yourself (uncompiled and untested code):
async Task<HttpResponseMessage> MyRetry(Func<Task<HttpResponseMessage>> action)
{
for (int retries = 0; retries < 3; ++retries)
{
var result = await action();
if (result.StatusCode != HttpStatusCode.Conflict)
return result;
}
return await action();
}
The above code will retry 3 times, for 4 total calls if it keeps returning Conflict.