/// <summary> /// Skip the question. Returns the next 2 questions like Answer but they aren't shown /// asynchronously - make the user wait for skipping questions. Doesn't update the /// answer cache. /// </summary> public async Task<JsonResult> Skip(AnswerViewModel input) { var profileId = GetMyProfileId(); var answer = new Answer { ProfileId = profileId, QuestionId = (short)input.QuestionId, ChoiceIndex = null, ChoiceAccept = 0, ChoiceWeight = 0, }; await _quesRepo.AnswerAsync(answer); var nextQuestions = _quesRepo.Next2Questions(profileId); return Json(nextQuestions); }
/// <summary> /// Add/Updates a user's answer in the database, and then calls MatchApiClient to /// update the cache. Responsible for keeping DB and cache in sync so wraps these /// operations in a transaction. /// /// - If getNextFlag = true get the next 2 questions and return them as JSON /// - Otherwise return the user's validated answer /// - Sets forceRecalculateMatches = true if user answered less than 10 questions (so new users can see results immediately) /// /// More info about TransactionScope (limitations): /// https://msdn.microsoft.com/en-us/data/dn456843.aspx /// /// </summary> public async Task<JsonResult> Answer(AnswerViewModel input, bool getNextFlag = true) { var profileId = GetMyProfileId(); _matchApiClient = GetMatchApiClient(); var answer = new Answer { ProfileId = profileId, QuestionId = (short)input.QuestionId }; //Convert checkboxes to bit array for Acceptable choices byte acceptBits = 0; foreach (var index in input.ChoiceAccept) { acceptBits |= (byte)(1 << (index - 1)); } answer.ChoiceIndex = (byte)input.ChoiceIndex; answer.ChoiceAccept = acceptBits; answer.ChoiceWeight = (byte)input.ChoiceImportance; //We should do some user input validation if(!_quesRepo.ValidateAnswer(answer)) { //invalid answer - return not OK Response.StatusCode = (int)HttpStatusCode.BadRequest; return Json(null); } // Isolation Level: Read Committed // Timeout : 5 minutes var options = new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromMinutes(5) }; int count; //Update DB and Cache in a transaction //By default if scope.Complete() isn't called the transaction is rolled back. using (var scope = new TransactionScope(TransactionScopeOption.Required, options, TransactionScopeAsyncFlowOption.Enabled)) { var t1 = _quesRepo.AnswerAsync(answer); var t2 = _matchApiClient.AnswerAsync(answer); //await Task.WhenAll(t1, t2); await t1; count = await t2; scope.Complete(); } //Update the Activity feed if(IsOkToAddActivity(OkbConstants.ActivityCategories.AnsweredQuestion)) { //Get the question text var ques = _quesRepo.GetQuestion(input.QuestionId); var choiceText = ""; choiceText = ques.Choices[((int)answer.ChoiceIndex - 1) % ques.Choices.Count]; _feedRepo.AnsweredQuestionActivity(profileId, ques.Text, choiceText); UpdateActivityLastAdded(OkbConstants.ActivityCategories.AnsweredQuestion); } //set forceRecalculateMatches = true if answered less than 10 questions if (count < 10) { Session[OkbConstants.FORCE_RECALCULATE_MATCHES] = true; } IList<QuestionModel> nextQuestions = null; //Get the next questions if (getNextFlag) { nextQuestions = _quesRepo.Next2Questions(profileId); return Json(nextQuestions); } else { return Json(answer); } }