Have you ever needed to create a screen to ask a user to select security questions? Well, I recently had to create this screen and I thought I’d share the experience.
I guess I’ll start with the view model.
public class CreateSecurityQuestionsView
{
public IList<SelectListItem> QuestionList
{
get { return DI.SecurityQuestionService().GenerateSecurityQuestions(); }
}
[ValidateNonEmpty("Username was not found")]
public string Username { get; set; }
[ValidateNonEmpty("")]
[ValidateNotSameAs("SecondQuestionId", "Question 1 = Question 2")]
[ValidateNotSameAs("ThirdQuestionId", "Question 1 = Question 3")]
public string FirstQuestionId { get; set; }
[ValidateNonEmpty("Answer 1 is Required")]
public string FirstAnswer { get; set; }
[ValidateNonEmpty("")]
[ValidateNotSameAs("FirstQuestionId", "Question 2 = Question 1")]
[ValidateNotSameAs("ThirdQuestionId", "Question 2 = Question 3")]
public string SecondQuestionId { get; set; }
[ValidateNonEmpty("Answer 2 is Required")]
public string SecondAnswer { get; set; }
[ValidateNonEmpty("")]
[ValidateNotSameAs("FirstQuestionId", "Question 3 = Question 1")]
[ValidateNotSameAs("SecondQuestionId", "Question 3 = Question 2")]
public string ThirdQuestionId { get; set; }
[ValidateNonEmpty("Answer 3 is Required")]
public string ThirdAnswer { get; set; }
}
So my view has a QuestionList, which gets all the questions for the end-user to select. I’ll populate the dropdownlists with this property in a bit. I used the goodsecurityquestions.com website to gather my questions and I’m using 15 in the selection. The other properties are pretty standard and the validation attributes are Castle Validators. I’m not a fan of the way the ValidateNotSameAs worked out for me, but it does the job.
Okay, let’s take a look at the HTML.
<%=Html.DivValidationSummary("All three questions are required and should be unique")%>
<form action="/Account/CreateSecurityQuestions/" method="post" id="step5form">
<fieldset>
<legend>Create Security Questions</legend>
<p><%=this.Select(f => f.FirstQuestionId)
.Options(Model.QuestionList)
.Label("Security Question 1: ")
.Selected(Model.FirstQuestionId)%></p>
<p><%=this.TextBox(f => f.FirstAnswer).Label("Answer 1: ")%></p>
<p><%=this.Select(f => f.SecondQuestionId)
.Options(Model.QuestionList)
.Label("Security Question 2: ")
.Selected(Model.SecondQuestionId)%></p>
<p><%=this.TextBox(f => f.SecondAnswer).Label("Answer 2: ")%></p>
<p><%=this.Select(f => f.ThirdQuestionId)
.Options(Model.QuestionList)
.Label("Security Question 3: ")
.Selected(Model.ThirdQuestionId)%></p>
<p><%=this.TextBox(f=>f.ThirdAnswer).Label("Answer 3: ") %></p>
<%=this.Hidden(f=>f.Username) %>
<%=Html.AntiForgeryToken() %>
<div style="text-align: right"><%=Html.SubmitButton("Finish", "sb", "S") %></div>
</fieldset>
</form>
So you can see here, I’m using fluentHtml and you can see the view model from above spread all through the markup. You should notice the Model.QuestionList three different times in the above markup in the Options() for the Select(). In order to use fluentHtml, you’ll need to inherit from the MvcContrib.FluentHtml.ModelViewPage<TModel>. I plan on doing a post on fluentHtml soon, which will go through the configuration and usage…etc.
The controller is really basic and looks like this:
[AcceptVerbs(HttpVerbs.Post), ValidateModel(typeof(CreateSecurityQuestionsView)), ValidateAntiForgeryToken]
public ActionResult CreateSecurityQuestions(CreateSecurityQuestionsView securityQuestionsView)
{
if (!ModelState.IsValid)
return View("Step5", securityQuestionsView);
_registrationService.CreateSecurityQuestions(Mapper.Map<CreateSecurityQuestionsView, AccountInformation>(securityQuestionsView));
return RedirectToAction("Summary");
}
Alrighty, so the CreateSecurityQuestions action is post only, it uses the ValidateModel filter that I got from CodeCampServer (I think), and it uses the ValidateAntiForgeryToken. You can read more about the ValidateAntiForgeryToken on Phil Haack’s post. Obviously the action accepts the CreateSecurityQuestionsView and then it checks to verify the ModelState is valid, if not, it returns the view. Then I’m using AutoMapper to map the view model to my AccountInformation object and then I redirect to the summary action. The _registrationService is passed into the controller via the constructor.
I did consider doing a loop on the security questions and answers, but I thought the most I’d ever have is 5 in the view and as long as it’s not a set number in my domain I’m alright with it. Basically, what I’m saying is don’t put securityQuestion1, 2, 3 etc in your domain model, allow it to grow to how many ever security questions your end-user wants to answer. The reason we’re using three is because we require our registered users to answer three and then they can add more later once they’ve completed the registration process.
I think that about covers everything. Please comment if you’d like more details on anything.
Thanks for reading!