Example #1
0
        /// <summary>
        /// Adds or updates a users answer. Creates new user if they're not in the cache,
        /// otherwise update answer. returns the number of questions answered.
        /// </summary>        
        public int AddOrUpdate(Answer updateAnswer)
        {
            List<CacheAnswer> listAnswers;

            // Check for new user
            if(!_answerCache.TryGetValue(updateAnswer.ProfileId, out listAnswers))
            {
                listAnswers = new List<CacheAnswer>();
                _answerCache.Add(updateAnswer.ProfileId, listAnswers);
            }

            //Check if updating or adding new answer
            for(int i=0; i < listAnswers.Count; i++)
            {
                var a = listAnswers[i];

                if(a.QuestionId==updateAnswer.QuestionId)
                {
                    a.ChoiceBit = updateAnswer.ChoiceBit();
                    a.ChoiceAccept = updateAnswer.ChoiceAccept;
                    a.ChoiceWeight = updateAnswer.ChoiceWeight;
                    a.LastAnswered = DateTime.Now;

                    //copy back to list since structs are passed by value
                    listAnswers[i] = a;

                    //found and updated answer, done
                    return listAnswers.Count;
                }
            }

            //Got here: New answer
            listAnswers.Add(new CacheAnswer
            {
                QuestionId = updateAnswer.QuestionId,
                ChoiceBit = updateAnswer.ChoiceBit(),
                ChoiceAccept = updateAnswer.ChoiceAccept,
                ChoiceWeight = updateAnswer.ChoiceWeight,
                LastAnswered = DateTime.Now
            });

            return listAnswers.Count;
        }
        /// <summary>
        /// Makes sure an answer is valid by meeting the following requirements.  Returns true 
        /// if answer is valid, false if not. Has side effect of modifiying passed in Answer.
        /// 
        ///     - Answer isn't skipped (ChoiceIndex is not 0 or null)
        ///     - Set Irrelevant values: If ChoiceWeight = 0 then all bits set to 1 on ChoiceAccept
        ///     - ChoiceWeight must be 0, 1, 2, or 3
        ///     - ChoiceAccept can't be 0
        /// 
        /// </summary>
        public bool ValidateAnswer(Answer ans)
        {
            if (ans.ChoiceIndex == null || ans.ChoiceIndex == 0) return false;

            //Irrelevant
            if (ans.ChoiceWeight == 0)
            {
                // user chose irrelevant, mark all answers as acceptable
                ans.ChoiceAccept = 0xFF;
            }

            if (ans.ChoiceWeight != 0 &&
                ans.ChoiceWeight != 1 &&
                ans.ChoiceWeight != 2 &&
                ans.ChoiceWeight != 3)
            {
                return false;
            }

            if (ans.ChoiceAccept == 0) return false;

            return true;
        }
        /// <summary>
        /// 
        /// Updates/Adds a users answer in the database. Called by AnswerQuestion which already
        /// validated the answer so we don't have to here.
        /// 
        /// </summary>
        protected async Task AnswerDbAsync(Answer ans)
        {
            var db = new OkbDbContext();

            var dbAns = await db.Answers.FindAsync(ans.ProfileId, ans.QuestionId);

            //Are we updating a question or adding a new one?
            if (dbAns == null)
            {
                //new answer
                ans.LastAnswered = DateTime.Now;
                db.Answers.Add(ans);
            }
            else
            {
                //update answer
                dbAns.ChoiceIndex = ans.ChoiceIndex;
                dbAns.ChoiceWeight = ans.ChoiceWeight;
                dbAns.ChoiceAccept = ans.ChoiceAccept;
                dbAns.LastAnswered = DateTime.Now;
            }

            await db.SaveChangesAsync();
        }
Example #4
0
        /// <summary>
        /// Simulations answering one question to see how fast we can insert answers into the DB.
        /// Should be called in a loop and given a Random object.
        /// </summary>
        public void SimulateAnsweringQuestion(Random rand)
        {
            var db = new OkbDbContext();
            var ans = new Answer
            {
                ProfileId = rand.Next(2, 200000),
                QuestionId = (short)rand.Next(201, 1243),
                ChoiceIndex = 1,
                ChoiceWeight = 1,
                ChoiceAccept = 1,
                LastAnswered = DateTime.Now
            };

            try
            {
                db.Answers.Add(ans);
                db.SaveChanges();
            }
            catch (Exception)
            {

                throw;
            }
        }
Example #5
0
 /// <summary>
 /// Returns true if the other answer matches my requirements
 /// </summary>
 public bool IsMatch(Answer otherAnswer)
 {
     return (otherAnswer.ChoiceBit() & ChoiceAccept) != 0;
 }
Example #6
0
        private static void ChoicePart2(TagBuilder tag, Answer answer, Answer compareAnswer, int index)
        {
            AcceptableChoice(tag, index, answer.ChoiceAccept);

            if (compareAnswer != null)
            {
                //we both answered this question - add comparison classes
                if (compareAnswer.ChoiceIndex == index) MatchChoice(tag, index, answer.ChoiceAccept);
            }
        }
Example #7
0
 private static void ChosenChoice(TagBuilder tag, int index, Answer answer)
 {
     if (index != answer.ChoiceIndex)
     {
         tag.AddCssClass("question-choice-fade");
     }
     else
     {
         tag.AddCssClass("question-choice-bold");
     }
 }
Example #8
0
        public static HtmlString ShowAnswersMe(this HtmlHelper htmlHelper, Answer answer, IList<string> choices)
        {
            var html = "";

            for (int i = 1; i <= choices.Count; i++)
            {
                var tag = new TagBuilder("li");
                tag.AddCssClass(answer.ChoiceIndex == i ? "question-choice-check" : "question-choice-bullet");
                tag.AddCssClass(answer.IsMatch(i) ? "question-choice-green" : "question-choice-strike");
                tag.InnerHtml = choices[i - 1];

                html += tag.ToString();
            }

            return new HtmlString(html);
        }
Example #9
0
 ///////////////////////////////////////////////////////////////////////////////////////
 public static HtmlString ShowAnswer(this HtmlHelper htmlHelper, Answer answer, Answer otherAnswer, IList<string> choices)
 {
     if (answer.ChoiceIndex == null || otherAnswer.ChoiceIndex == null)
     {
         return new HtmlString("INVALID ANSWER");
     }
     var tag = new TagBuilder("span");
     tag.AddCssClass(otherAnswer.IsMatch(answer) ? "question-choice-green" : "question-choice-red");
     tag.InnerHtml = choices[((int)answer.ChoiceIndex - 1) % choices.Count];
     return new HtmlString(tag.ToString());
 }
 /// <summary>
 /// Answers a question by adding it to the db. Will update if answer exists, otherwise adds a new
 /// answer. The controller will take care of updating answer in cache. Validation peformed by the caller.
 /// </summary>
 public async Task AnswerAsync(Answer ans)
 {
     await AnswerDbAsync(ans);
 }
Example #11
0
        /// <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);
        }
Example #12
0
        /// <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);
            }            
        }