Ejemplo n.º 1
0
        private (bool, string) FormSubmitHandler(User user, UserFormSubmission submission)
        {
            if (submission?.SubForms?.Count == ObjectList.Count)
            {
                for (int i = 0; i < submission.SubForms.Count; i++)
                {
                    T extractedAnswer = AnswerExtractor(submission.SubForms[i]);
                    if (extractedAnswer != null)
                    {
                        if (ObjectList[i].UserAnswers == null)
                        {
                            ObjectList[i].UserAnswers = new List <QueryInfo <T> >();
                        }
                        ObjectList[i].UserAnswers.Add(new QueryInfo <T>()
                        {
                            UserQueried   = user,
                            TimeTakenInMs = DateTime.UtcNow.Subtract(this.StartingTime).TotalMilliseconds,
                            Answer        = extractedAnswer,
                        });
                    }
                }
            }

            return(true, string.Empty);
        }
Ejemplo n.º 2
0
        /// <summary>
        /// This is the outlet function that Exit will call
        /// </summary>
        private void Outlet(User user, UserStateResult result, UserFormSubmission input)
        {
            if (this.FirstUser)
            {
                lock (this.FirstUserLock)
                {
                    if (this.FirstUser)
                    {
                        foreach (var listener in this.StateEndingListeners)
                        {
                            listener?.Invoke();
                        }
                        this.FirstUser = false;
                    }
                }
            }

            lock (user)
            {
                foreach (var listener in this.PerUserStateEndingListeners)
                {
                    listener?.Invoke(user);
                }
            }

            if (this.UserOutletOverrides.TryGetValue(user, out Connector connector))
            {
                connector(user, result, input);
            }
            else
            {
                this.InternalOutlet(user, result, input);
            }
        }
Ejemplo n.º 3
0
        public async Task SubmitUserForm(UserPrompt prompt, UserFormSubmission submission, string userId)
        {
            if (submission == null)
            {
                return;
            }
            submission.Id = prompt.Id;
            for (int i = 0; i < (submission.SubForms?.Count ?? 0); i++)
            {
                submission.SubForms[i].Id = prompt.SubPrompts?[i]?.Id ?? Guid.Empty;
            }
            string path = Constants.Path.FormSubmit;

            // If set up to auto submit and randomly selected to auto submit.
            if ((prompt.AutoSubmitAtTime != null) && (Rand.NextDouble() < this.AutoSubmitPercentage))
            {
                path = Constants.Path.AutoFormSubmit;
            }

            HttpResponseMessage httpResponseMessage = await MakeWebRequest(
                path: path,
                userId : userId,
                method : HttpMethod.Post,
                content : new StringContent(
                    JsonConvert.SerializeObject(submission),
                    Encoding.UTF8,
                    Constants.MediaType.ApplicationJson));

            await httpResponseMessage.ThrowIfNonSuccessResponse(userId);
        }
Ejemplo n.º 4
0
 public void Inlet(User user, UserStateResult result, UserFormSubmission formSubmission)
 {
     if (!this.UsersInLobby.ContainsKey(user.Id))
     {
         throw new Exception("User not registered for this lobby");
     }
     this.WaitForLobbyStart.Inlet(user, result, formSubmission);
 }
Ejemplo n.º 5
0
 public override (bool, string) CountingFormSubmitHandler(User user, UserFormSubmission input, int counter)
 {
     this.Drawings.Add(new PeopleUserDrawing
     {
         Drawing = input.SubForms[0].Drawing,
         Owner   = user,
         Type    = UsersToRandomizedBodyPartTypes[user][counter % 3]
     });
     return(true, string.Empty);
 }
        public IActionResult Post(
            [FromBody] UserFormSubmission formData,
            string id)
        {
            if (!ModelState.IsValid)
            {
                return(new BadRequestResult());
            }

            if (!Sanitize.SanitizeString(id, out string error, Constants.RegexStrings.UserId, 50, 50))
            {
                return(BadRequest(error));
            }

            User user = GameManager.MapIdentifierToUser(id, out bool newUser);

            if (user != null)
            {
                user.LastSubmitTime = DateTime.UtcNow;
            }

            if (user?.UserState == null || newUser)
            {
                return(BadRequest("Error finding user object, try again."));
            }

            if (!Sanitize.SanitizeAllStrings(formData, out error))
            {
                return(BadRequest(error));
            }

            // Make sure HandleUserFormInput is never called concurrently for the same user.
            bool success = false;

            try
            {
                lock (user.LockObject)
                {
                    // If user form input was valid, handle it, else return the error.
                    if (user.UserState.CleanUserFormInput(user, ref formData, out error) == UserState.CleanUserFormInputResult.Valid)
                    {
                        success = user.UserState.HandleUserFormInput(user, formData, out error);
                    }
                }
            }
            catch (Exception e)
            {
                error   = "An unexpected error occurred, refresh and try again :(";
                success = false;

                // Let GameManager know so it can determine whether or not to abandon the lobby.
                GameManager.ReportGameError(ErrorType.UserSubmit, user?.LobbyId, user, e);
            }
            return(success ? new OkResult() : BadRequest(error));
        }
Ejemplo n.º 7
0
        public IActionResult Post(
            [FromBody] UserFormSubmission formData,
            string id)
        {
            if (!Sanitize.SanitizeString(id, out string error, Constants.RegexStrings.UserId, 50, 50))
            {
                return(BadRequest(error));
            }

            User user = GameManager.MapIdentifierToUser(id, out bool newUser);

            if (user != null)
            {
                user.LastSubmitTime = DateTime.UtcNow;
            }

            if (user?.UserState == null || newUser)
            {
                return(BadRequest("Error finding user object, try again."));
            }

            if (!Sanitize.SanitizeAllStrings(formData, out error))
            {
                return(BadRequest(error));
            }

            // Make sure HandleUserFormInput is never called concurrently for the same user.
            try
            {
                lock (user.LockObject)
                {
                    if (!user.UserState.UserRequestingCurrentPrompt(user).SubmitButton)
                    {
                        return(BadRequest("You shouldn't be able to time out on this prompt."));
                    }

                    // Cleans invalid fields and replaces with null.
                    if (user.UserState.CleanUserFormInput(user, ref formData, out error) != UserState.CleanUserFormInputResult.Invalid)
                    {
                        // Handles partial/null inputs.
                        user.UserState.HandleUserTimeout(user, formData);
                    }
                }
            }
            catch (Exception e)
            {
                error = "An unexpected error occurred, refresh and try again :(";

                // Let GameManager know so it can determine whether or not to abandon the lobby.
                GameManager.ReportGameError(ErrorType.UserSubmit, user?.LobbyId, user, e);

                return(new BadRequestObjectResult(error));
            }
            return(new OkResult());
        }
Ejemplo n.º 8
0
        public override UserTimeoutAction CountingUserTimeoutHandler(User user, UserFormSubmission input, int counter)
        {
            if (input?.SubForms?.Count == 3 &&
                input.SubForms[0].ShortAnswer != null &&
                input.SubForms[1].ShortAnswer != null &&
                input.SubForms[2].ShortAnswer != null)
            {
                HandleInput(user, input);
            }

            return(UserTimeoutAction.None);
        }
Ejemplo n.º 9
0
        public async Task GetPromptAndSubmitUserForm(Func <UserPrompt, UserFormSubmission> handler, string userId)
        {
            HttpResponseMessage currentContentResponse = await MakeWebRequest(
                path : Constants.Path.CurrentContent,
                userId : userId,
                method : HttpMethod.Get);

            UserPrompt prompt = JsonConvert.DeserializeObject <UserPrompt>(await currentContentResponse.Content.ReadAsStringAsync());

            UserFormSubmission submission = handler(prompt);

            await SubmitUserForm(prompt, submission, userId);
        }
Ejemplo n.º 10
0
 public override UserTimeoutAction CountingUserTimeoutHandler(User user, UserFormSubmission input, int counter)
 {
     if (input?.SubForms?[0]?.Drawing != null)
     {
         Drawings.Add(new PeopleUserDrawing
         {
             Drawing = input.SubForms[0].Drawing,
             Owner   = user,
             Type    = UsersToRandomizedBodyPartTypes[user][counter % 3]
         });
     }
     return(UserTimeoutAction.None);
 }
Ejemplo n.º 11
0
 public override UserTimeoutAction CountingUserTimeoutHandler(User user, UserFormSubmission input, int counter)
 {
     if (input?.SubForms?[0]?.ShortAnswer != null &&
         !Prompts.Select((prompt) => prompt.Text).Contains(input.SubForms[0].ShortAnswer))
     {
         Prompts.Add(
             new Prompt()
         {
             Owner = user,
             Text  = input.SubForms[0].ShortAnswer
         });
     }
     return(UserTimeoutAction.None);
 }
Ejemplo n.º 12
0
 public override (bool, string) CountingFormSubmitHandler(User user, UserFormSubmission input, int counter)
 {
     if (Prompts.Select((prompt) => prompt.Text).Contains(input.SubForms[0].ShortAnswer))
     {
         return(false, "Someone has already entered that prompt");
     }
     Prompts.Add(
         new Prompt()
     {
         Owner = user,
         Text  = input.SubForms[0].ShortAnswer
     });
     return(true, String.Empty);
 }
Ejemplo n.º 13
0
 /// <summary>
 /// The inlet to the transition.
 /// </summary>
 /// <param name="user">The user to move into the transition.</param>
 /// <param name="stateResult">The state result of the last node (this transition doesnt care).</param>
 /// <param name="formSubmission">The user input of the last node (this transition doesnt care).</param>
 public override void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission)
 {
     base.Inlet(user, stateResult, formSubmission);
     if (firstUser)
     {
         lock (firstUserLock)
         {
             if (firstUser)
             {
                 this.delayTriggerTask = DelayedTrigger(this.Delay);
                 firstUser             = false;
             }
         }
     }
 }
Ejemplo n.º 14
0
        private T TryExtractObjectVotedFor(User user, UserFormSubmission submission)
        {
            SubPrompt subPrompt      = this.PromptsPerUser[user].SubPrompts[0];
            T         objectVotedFor = null;

            if (subPrompt?.Answers != null && (submission?.SubForms?.FirstOrDefault()?.RadioAnswer != null))
            {
                objectVotedFor = this.ObjectList[submission.SubForms.FirstOrDefault().RadioAnswer.Value];
            }
            else if (subPrompt?.Selector?.ImageList != null && (submission?.SubForms?.FirstOrDefault()?.Selector != null))
            {
                objectVotedFor = this.ObjectList[submission.SubForms[0].Selector.Value];
            }
            return(objectVotedFor);
        }
Ejemplo n.º 15
0
        /// <summary>
        /// The inlet to the transition.
        /// </summary>
        /// <param name="user">The user to move into the transition.</param>
        /// <param name="stateResult">The state result of the last node (this transition doesnt care).</param>
        /// <param name="formSubmission">The user input of the last node (this transition doesnt care).</param>
        public override void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission)
        {
            base.Inlet(user, stateResult, formSubmission);
            this.UsersWaiting.Add(user);

            // TODO: locks in this flow almost certainly have race conditions.

            // Will recalculate active users on each submission. Slight "bug" if last user becomes inactive we still wait out the timer.

            // Hurry users once all active have submitted. IFF that is the selected mode
            if (this.UsersToWaitForType == WaitForUsersType.Active &&
                !this.Hurried &&
                this.GetUsers(WaitForUsersType.Active).IsSubsetOf(this.UsersWaiting))
            {
                lock (this.TriggeredLock)
                {
                    // UsersToWaitForType cannot change.
                    if (!this.Hurried &&
                        this.GetUsers(WaitForUsersType.Active).IsSubsetOf(this.UsersWaiting))
                    {
                        this.Hurried = true;
                        this.ParentState.HurryUsers();
                    }
                }
            }

            // Proceed to next state once we have all users.
            if (!this.Triggered && this.GetUsers(WaitForUsersType.All).IsSubsetOf(this.UsersWaiting))
            {
                bool triggeringThread = false;
                lock (this.TriggeredLock)
                {
                    if (!this.Triggered && this.GetUsers(WaitForUsersType.All).IsSubsetOf(this.UsersWaiting))
                    {
                        this.Triggered   = true;
                        triggeringThread = true;
                    }
                }

                // Cannot call this from within a lock, can only be called by one thread. Other threads will just go into
                // waiting mode :)
                if (triggeringThread)
                {
                    this.Trigger();
                }
            }
        }
Ejemplo n.º 16
0
        public void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission)
        {
            // If there is no submit button the user is not meant to answer prompts or hurry through.
            if (!AwaitingInput(user))
            {
                return;
            }

            // Set user to answering prompts state if they arent being hurried and their current prompt has a submit button.
            if (user.StatesTellingMeToHurry.Count == 0)
            {
                user.Status = UserStatus.AnsweringPrompts;
            }
            else
            {
                // TODO: May need to pass in prompt rather than getting from user.UserState but should be okay for now.
                HandleUserTimeout(user, UserFormSubmission.WithNulls(user.UserState?.UserRequestingCurrentPrompt(user)));
            }
        }
Ejemplo n.º 17
0
        private (bool, string) FormSubmitHandler(User user, UserFormSubmission submission)
        {
            T objectVotedFor = TryExtractObjectVotedFor(user, submission);

            objectVotedFor.Should().NotBeNull(because: "Could not find an object voted for");
            VoteInfo vote = new VoteInfo
            {
                ObjectsVotedFor = new List <object> {
                    objectVotedFor
                },
                TimeTakenInMs = DateTime.UtcNow.Subtract(this.StartingTime).TotalMilliseconds,
                UserWhoVoted  = user
            };

            objectVotedFor.VotesCastForThisObject.Add(vote);
            this.UserVotes[user] = vote;

            return(true, string.Empty);
        }
Ejemplo n.º 18
0
        private void HandleInput(User user, UserFormSubmission input)
        {
            Question question;

            int    leftLabelInt;
            int    rightLabelInt;
            string leftLabel  = input?.SubForms[1]?.ShortAnswer;
            string rightLabel = input?.SubForms[2]?.ShortAnswer;

            if (int.TryParse(leftLabel, out leftLabelInt) &&
                int.TryParse(rightLabel, out rightLabelInt) &&
                leftLabelInt < rightLabelInt &&
                rightLabelInt - leftLabelInt <= FriendQuizConstants.MaxSliderTickRange)
            {
                question = new Question()
                {
                    Owner      = user,
                    Text       = input.SubForms[0].ShortAnswer,
                    MinBound   = leftLabelInt,
                    MaxBound   = rightLabelInt,
                    Numeric    = true,
                    TickLabels = new List <string>()
                    {
                        leftLabel, rightLabel
                    }
                };
            }
            else
            {
                question = new Question()
                {
                    Owner      = user,
                    Text       = input.SubForms[0].ShortAnswer,
                    TickLabels = new List <string>()
                    {
                        leftLabel, rightLabel
                    }
                };
            }

            RoundTracker.Questions.Add(question);
        }
Ejemplo n.º 19
0
        private UserTimeoutAction FormTimeoutHandler(User user, UserFormSubmission submission)
        {
            T objectVotedFor = TryExtractObjectVotedFor(user, submission);

            // TODO: Support ability to not count a vote.
            if (objectVotedFor == null)
            {
                objectVotedFor = ObjectList.First();
            }
            VoteInfo vote = new VoteInfo
            {
                ObjectsVotedFor = new List <object> {
                    objectVotedFor
                },
                TimeTakenInMs = DateTime.UtcNow.Subtract(this.StartingTime).TotalMilliseconds,
                UserWhoVoted  = user
            };

            objectVotedFor.VotesCastForThisObject.Add(vote);
            this.UserVotes[user] = vote;
            return(UserTimeoutAction.None);
        }
Ejemplo n.º 20
0
        public static CleanUserFormInputResult CleanUserFormInput(UserPrompt userPrompt, ref UserFormSubmission userInput, out string error)
        {
            // No data submitted / requested
            if (userInput == null || userPrompt == null)
            {
                error     = "Try again or try refreshing the page.";
                userInput = UserFormSubmission.WithNulls(userPrompt);
                return(CleanUserFormInputResult.Invalid);
            }

            // Old data submitted.
            if (userInput.Id != userPrompt.Id)
            {
                error     = "Outdated form submitted, try again or try refreshing the page.";
                userInput = UserFormSubmission.WithNulls(userPrompt);
                return(CleanUserFormInputResult.Invalid);
            }

            // No prompts requested.
            if (userPrompt?.SubPrompts == null || userPrompt.SubPrompts.Length == 0)
            {
                userInput = UserFormSubmission.WithNulls(userPrompt);
                error     = "";
                return(CleanUserFormInputResult.Valid);
            }

            if (userInput?.SubForms == null || (userPrompt.SubPrompts.Length != userInput.SubForms.Count))
            {
                error     = "Error in submission, try again or try refreshing the page.";
                userInput = UserFormSubmission.WithNulls(userPrompt);
                return(CleanUserFormInputResult.Invalid);
            }

            int i = 0;
            CleanUserFormInputResult result = CleanUserFormInputResult.Valid;

            error = string.Empty;
            foreach (SubPrompt prompt in userPrompt?.SubPrompts ?? new SubPrompt[0])
            {
                UserSubForm subForm = userInput.SubForms[i];
                if (!(PromptInputValidation.Validate_Drawing(prompt, subForm) &&
                      PromptInputValidation.Validate_ShortAnswer(prompt, subForm) &&
                      PromptInputValidation.Validate_ColorPicker(prompt, subForm) &&
                      PromptInputValidation.Validate_Answers(prompt, subForm) &&
                      PromptInputValidation.Validate_Selector(prompt, subForm) &&
                      PromptInputValidation.Validate_Dropdown(prompt, subForm) &&
                      PromptInputValidation.Validate_Slider(prompt, subForm)))
                {
                    error  = "Not all form fields have been filled out";
                    result = CleanUserFormInputResult.Cleaned;

                    // Invalid fields get set to null (used in autosubmit partial submission flows).
                    userInput.SubForms[i] = new UserSubForm()
                    {
                        Id = prompt.Id
                    };
                }

                i++;
            }

            return(result);
        }
Ejemplo n.º 21
0
 public void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission)
 {
     this.Exit.Inlet(user, stateResult, formSubmission);
 }
Ejemplo n.º 22
0
 public override void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission)
 {
     this.WaitingState.Inlet(user, stateResult, formSubmission);
 }
Ejemplo n.º 23
0
        public CleanUserFormInputResult CleanUserFormInput(User user, ref UserFormSubmission userInput, out string error)
        {
            UserPrompt userPrompt = GetUserPromptHolder(user).Prompt;

            return(CleanUserFormInput(userPrompt, ref userInput, out error));
        }
Ejemplo n.º 24
0
 public abstract UserTimeoutAction CountingUserTimeoutHandler(User user, UserFormSubmission input, int counter);
Ejemplo n.º 25
0
 public void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission)
 {
     this.InternalConnector?.Invoke(user, stateResult, formSubmission);
 }
Ejemplo n.º 26
0
        // TODO: configure delayBetweenSubmissions? - Command line arg.

        public virtual async Task RunTest()
        {
            int       i        = 0;
            const int maxIters = 500;
            // Reset data structures so that they always show current GameStep.
            Dictionary <LobbyPlayer, Task <UserPrompt> > playerPrompts = new Dictionary <LobbyPlayer, Task <UserPrompt> >();
            List <Task> playerSubmissions = new List <Task>();

            try
            {
                for (i = 0; i < maxIters; i++)
                {
                    // Reset data structures so that they always show current GameStep.
                    playerPrompts     = new Dictionary <LobbyPlayer, Task <UserPrompt> >();
                    playerSubmissions = new List <Task>();

                    DateTime pollingEnd = DateTime.UtcNow.Add(this.MaxTotalPollingTime);
                    // Keep polling current prompts
                    while (!playerPrompts.Values.Any(val => val.Result.SubmitButton))
                    {
                        if (DateTime.UtcNow > pollingEnd)
                        {
                            throw new Exception("Ran out of time polling, did game soft-lock?");
                        }

                        // Delay not needed on first iteration.
                        if (playerPrompts.Count > 0)
                        {
                            Thread.Sleep((int)this.PollingDelay.TotalMilliseconds);
                        }

                        playerPrompts = new Dictionary <LobbyPlayer, Task <UserPrompt> >();
                        foreach (LobbyPlayer player in Lobby.Players)
                        {
                            playerPrompts.Add(player, this.WebClient.GetUserPrompt(player.UserId));
                        }

                        await Task.WhenAll(playerPrompts.Values);
                    }

                    // HACKY FIX. Above is checking if ANY user has a prompt. To avoid race condition. wait a little and check everybody again.
                    Thread.Sleep((int)this.PollingDelay.TotalMilliseconds);
                    playerPrompts = new Dictionary <LobbyPlayer, Task <UserPrompt> >();
                    foreach (LobbyPlayer player in Lobby.Players)
                    {
                        playerPrompts.Add(player, this.WebClient.GetUserPrompt(player.UserId));
                    }

                    await Task.WhenAll(playerPrompts.Values);

                    // END HACKY FIX

                    var prompts = playerPrompts.Values.Select(val => val.Result);

                    // Check if game end
                    if (prompts.Any(prompt => prompt.UserPromptId == UserPromptId.PartyLeader_GameEnd))
                    {
                        Console.WriteLine("Game Finished");
                        break;
                    }

                    // Validate current set is expected.
                    if (this is IStructuredTest)
                    {
                        var validations = ((IStructuredTest)this).UserPromptIdValidations;
                        if (i > validations.Count)
                        {
                            throw new Exception($"Game has gone past all structured test validations without ending.");
                        }

                        this.ValidatePrompts(validations[i], prompts);
                    }

                    foreach ((LobbyPlayer player, Task <UserPrompt> promptTask) in playerPrompts)
                    {
                        Thread.Sleep((int)this.DelayBetweenSubmissions.TotalMilliseconds);
                        UserPrompt prompt = promptTask.Result;

                        UserFormSubmission submission = HandleUserPrompt(prompt, player, i);

                        bool providedSubmission = submission != null;
                        if (!prompt.SubmitButton && providedSubmission)
                        {
                            throw new Exception($"Test's 'HandleUserPrompt' provided a UserFormSubmission when it was not expected. UserId='{player.UserId}'");
                        }

                        if (prompt.SubmitButton && !providedSubmission)
                        {
                            throw new Exception($"Test's 'HandleUserPrompt' did not provide a UserFormSubmission when one was expected. UserId='{player.UserId}'");
                        }

                        if (submission != null)
                        {
                            playerSubmissions.Add(this.WebClient.SubmitUserForm(prompt, submission, player.UserId));
                        }
                    }
                    await Task.WhenAll(playerSubmissions);

                    // Validate game was expected to end here
                    if (i >= maxIters)
                    {
                        throw new Exception($"Test runner exceeded ({i}) max game steps of ({maxIters})");
                    }
                }

                if ((this is IStructuredTest) && (i != ((IStructuredTest)this).UserPromptIdValidations.Count))
                {
                    throw new Exception($"Game ended unexpectedly. Expected ({((IStructuredTest)this).UserPromptIdValidations.Count}) game steps, actual:({i})");
                }
            }
            catch (Exception e)
            {
                e.Data.Add(Constants.ExceptionDataKeys.GameStep, i);

                // Try and add additional data points
                try
                {
                    if (this is IStructuredTest)
                    {
                        e.Data.Add(Constants.ExceptionDataKeys.Validations, ((IStructuredTest)this).UserPromptIdValidations.Select(var => var.PrettyPrint()));
                    }

                    e.Data.Add(Constants.ExceptionDataKeys.Prompts, $"*[{i}]:{SummarizePrompts(playerPrompts.Values.Select(task => task.Result)).PrettyPrint()}");
                }
                catch
                {
                    // Empty
                }
                throw;
            }
        }
Ejemplo n.º 27
0
 public abstract (bool, string) CountingFormSubmitHandler(User user, UserFormSubmission input, int counter);
Ejemplo n.º 28
0
 public override (bool, string) CountingFormSubmitHandler(User user, UserFormSubmission input, int counter)
 {
     HandleInput(user, input);
     return(true, string.Empty);
 }
Ejemplo n.º 29
0
 public virtual void Inlet(User user, UserStateResult stateResult, UserFormSubmission formSubmission)
 {
     this.InvokeEntranceListeners(user);
     this.InternalOutletConnector(user, stateResult, formSubmission);
 }
Ejemplo n.º 30
0
 /// <summary>
 /// This implementation validates user input, and should be overridden (with a call to base.HandlerUserFormInput)
 /// </summary>
 /// <param name="userInput">Validates the users form input and decides what UserStateResult to return to StatecompletedCallback.</param>
 /// <returns>True if the user input was accepted, false if there was an issue.</returns>
 public abstract bool HandleUserFormInput(User user, UserFormSubmission userInput, out string error);