We’re overwhelmed with tutorials and guides about .NET development. There’s a plethora of how-to guides on every aspect of MVC. I’ve written plenty myself! The trouble is, we don’t just focus on a bit of it, if we’re building a website. We need to understand all those bits, for sure. More than that though, we need to understand how they all fit together. That’s the tricky part. There are not as many articles around like that.
This is the first article in a series of end-to-end examples. In each one, we’ll take a business requirement and work it through from end-to-end. This week, we’re looking at unique username verification. Here’s our requirement:
In order to ensure that my chosen username is not in use already, as a new user of End To End Examples Inc™, I want to see if someone has taken the username I enter.
Let’s include some acceptance criteria, which help define the requirement:
- Verification must not happen until the user has finished entering his/her username
- Verification must not happen if the user leaves the field blank
- The user must see that verification is taking place
- The user must see success if the username is available
- The user must see an error if the username is not available
Got an example you’d like me to explain end-to-end? Let me know in the comments!
A Little Housekeeping
The first thing we need is a new MVC project. Crack open Visual Studio and create one. I’m calling mine EndToEndExamples
. Let’s stick with standard MVC and no authentication. Make sure you include Web API libraries as well, because we’ll need them.
Now that’s created, let’s tidy it up a bit. We can lose the standard About
and Contact
views. Delete Views/Home/About.cshtml and Views/Home/Contact.cshtml. Delete the About()
and Contact()
methods from HomeController
. Remove the action links to those 2 pages from _Layout.cshtml as well. They’re in the navbar.
I’ve changed the default namespace for my project to Levelnis.Learning.EndToEndExamples.Web
. If you make a similar change, don’t forget to update the Web.config
in the Views folder. My system.web.webPages.razor
section looks like this:
<system.web.webPages.razor>
<host factoryType="System.Web.Mvc.MvcWebRazorHostFactory, System.Web.Mvc,
Version=5.2.3.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35" />
<pages pageBaseType="System.Web.Mvc.WebViewPage">
<namespaces>
<add namespace="System.Web.Mvc" />
<add namespace="System.Web.Mvc.Ajax" />
<add namespace="System.Web.Mvc.Html" />
<add namespace="System.Web.Optimization"/>
<add namespace="System.Web.Routing" />
<add namespace="Levelnis.Learning.EndToEndExamples.Web" />
</namespaces>
</pages>
</system.web.webPages.razor>
See the custom namespace at the bottom? That needs to match the default namespace for your project. If it doesn't, you'll run into problems within your views. Now we’ve got a project, let’s think about what we’re about to build. Remember, we’re working on a specific business requirement here. We shouldn’t add anything outside of that requirement. So, what steps do we need to perform?
- Add a username textbox to the view, to capture user input
- Add some client-side code to show the user that something’s happening
- Add a Web API controller to receive that input
- Add some logic to check if the
username
exists and send back an indication
- Add some client-side code to send the input to the server and receive the response
- Add some client-side code to display the most appropriate indicator
Step 1: Add a Textbox to the View
Let’s add a form to the Index
view so we can get things moving. We’ll start by adding a ViewModel
to our Index
view:
namespace Levelnis.Learning.EndToEndExamples.Web.Models
{
public class IndexViewModel
{
public string Username { get; set; }
}
}
Let’s update the controller, so we can pass the model to the view:
namespace Levelnis.Learning.EndToEndExamples.Web.Controllers
{
using System.Web.Mvc;
using Models;
public class HomeController : Controller
{
public ActionResult Index()
{
var model = new IndexViewModel();
return View(model);
}
}
}
We also need to add the model and a textbox
to the view. Here’s what Index.cshtml now looks like:
@model Levelnis.Learning.EndToEndExamples.Web.Models.IndexViewModel
@{
ViewBag.Title = "Home Page";
}
<div class="row">
<div class="col-sm-8 col-xs-12">
<h3>Duplicate username validator</h3>
<form>
<div class="form-group">
@Html.LabelFor(m => m.Username)
@Html.TextBoxFor(m => m.Username, new { @class = "form-control" })
</div>
</form>
</div>
</div>
Step 2: Show the User that Something’s Happening
Nice and simple. When we hook everything up, we’ll need an indicator to show whether the username is available. We’ll use font awesome for this, so we need to add it. It’s a Nuget package, so go ahead and run this command from the Package Manager Console:
Install-Package FontAwesome
We can now add some icons to our form. These will show whether we find the username
or not. We’ll make the form an inline form as well. Change the Index.cshtml form code to this:
<form class="form-inline">
<div class="form-group">
@Html.LabelFor(m => m.Username)
</div>
<div class="form-group">
@Html.TextBoxFor(m => m.Username, new { @class = "form-control" })
</div>
<span class="hide" id="Indicator">
<i class="fa fa-circle-o-notch fa-spin fa-lg"> </i> </span>
<span class="hide btn btn-success"
id="IndicatorOk"> <i class="fa fa-check fa-lg">
</i> </span>
<span class="hide btn btn-danger" id="IndicatorError">
<i class="fa fa-times fa-lg"> </i> </span>
</form>
View the original article.
Notice we’ve got 3 hidden spans, which we’ll toggle as necessary. We now need to show the spinner after we enter some text. We’ll hook into the onblur
event for this. Remember our acceptance criteria? We need to make sure we only run this check if there’s data in the username
field. Let’s add a scripts
section to the Index
view:
@section scripts{
<script>
(function($) {
$('#Username').on('blur',
function () {
var username = $(this).val();
if (username.length > 0) {
$('#Indicator').removeClass('hide');
}
});
}(jQuery))
</script>
Step 3: Add a Web API Controller
We’ll leave the client for a moment and take a look at the API controller. This is where we’ll decide whether the username is available or not. Add an empty Web API 2 controller to Controllers/Api (create the folder if it doesn’t exist). Here’s mine:
using System.Web.Http;
namespace Levelnis.Learning.EndToEndExamples.Web.Controllers.Api
{
public class VerifyUsernameController : ApiController
{
public IHttpActionResult Get()
{
return Ok("OK");
}
}
}
If I now browse to api/verifyusername
, I see the OK string
:
<string xmlns="http://schemas.microsoft.com/2003/10/Serialization/">OK</string>
Wait a second, though. Wouldn’t it be better if that was JSON instead of XML? We can fix that in a jiffy. We need to add a formatter. Add a BrowserJsonFormatter
class to your project:
namespace Levelnis.Learning.EndToEndExamples.Web
{
using System;
using System.Net.Http.Formatting;
using System.Net.Http.Headers;
public class BrowserJsonFormatter : JsonMediaTypeFormatter
{
public BrowserJsonFormatter()
{
SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/html"));
}
public override void SetDefaultContentHeaders
(Type type, HttpContentHeaders headers, MediaTypeHeaderValue mediaType)
{
base.SetDefaultContentHeaders(type, headers, mediaType);
headers.ContentType = new MediaTypeHeaderValue("application/json");
}
}
}
Now that’s in place, we need a single line in the WebApiConfig
to use it. Pop this as the first line in the Register
method:
config.Formatters.Add(new BrowserJsonFormatter());
Now, when you refresh the page, you should see the string
“OK
”, instead of that clunky XML. So far so good. We need to pass a username
into our method next. Let’s add a parameter to the Get
method:
public IHttpActionResult Get(string username)
{
return Ok(username);
}
Step 4: Check If the username Exists
This just checks we’re seeing the parameter we pass in. Now let’s do something useful with it. In a real-life system, we’d go to a database at this point. We’d have a bunch of business rules to satisfy around username
validity. We might even suggest alternatives if the username
is gone. We’re not doing any of that though. We’ll keep our example nice and simple. We’ll use a JSON data source. Add a UsernameData
class to the Api folder:
namespace Levelnis.Learning.EndToEndExamples.Web.Controllers.Api
{
using System.Collections.Generic;
public class UsernameData
{
public const string DataSource = @"
{
""Usernames"":[
""smiler"",
""dave.smith"",
""happygolucky17"",
""tom2017"",
""ab1234"",
]
}";
public IList<string> Usernames { get; set; }
}
}
All we’ve got here is a string
of JSON data, which we’ll deserialise into a UsernameData
object. We’ll use JSON.NET for this. All our JSON data contains is a bunch of string
s in an array. As long as we have somewhere for JSON.NET to put those string
s when it deserialises, we’ll be fine. The Usernames
property does that for us. We need a VerifyUsernameResponse
to hold the data we send back from our controller:
public class VerifyUsernameResponse
{
public string OriginalUsername { get; set; }
public bool IsAvailable { get; set; }
}
Let’s update the Get
method:
namespace Levelnis.Learning.EndToEndExamples.Web.Controllers.Api
{
using System.Linq;
using System.Web.Http;
using Newtonsoft.Json;
public class VerifyUsernameController : ApiController
{
public IHttpActionResult Get(string username)
{
var usernameData =
JsonConvert.DeserializeObject<UsernameData> (UsernameData.DataSource);
var usernames = usernameData.Usernames.Select(x => x.ToLower());
var isUsernameTaken = usernames.Contains(username.ToLower());
var response = new VerifyUsernameResponse
{
OriginalUsername = username,
IsAvailable = !isUsernameTaken
};
return Ok(response);
}
}
}
What’s going on here? The first line uses JSON.NET to parse the JSON string
into a UsernameData
object. This works because our UsernameData
class contains a Usernames
property, which is a list of string
s. After that, we check if the username
is in our list or not. I'm mapping the original username
here so we can see it make the round trip.
Step 5: Send the username to the API and Grab the Response
Back we go to the client now. We need to make an AJAX call. Update the blur
event handler to call the API, like so:
$('#Username').on('blur',
function () {
var username = $(this).val();
if (username.length > 0) {
$('#Indicator').removeClass('hide');
$.ajax({
url: '/api/verifyusername',
method: 'GET',
data: { username: username }
}).done(function(response) {
$('#Indicator').addClass('hide');
});
}
});
At this stage, we’re making the call to the API. We’re then hiding the working indicator once we’ve got the response. The final piece is to display the appropriate indicator. Let’s look at that next.
Step 6: Display the Appropriate Indicator
The final step is to show success or failure as appropriate. To see this in action, I’ve added a wait of 1 second to the API call. If you want to do the same, add this to the top of the API Get
method:
Thread.Sleep(1000);
That will allow you to see the working indicator before the success or failure one takes its place. Otherwise, it all happens too quickly. Here are the final changes to the script:
@section scripts{
<script>
(function ($) {
var resetIndicators = function() {
$('#Indicator').addClass('hide');
$('#IndicatorOk').addClass('hide');
$('#IndicatorError').addClass('hide');
$('#IndicatorLabel').addClass('hide');
}
$('#Username').on('blur',
function () {
resetIndicators();
var username = $(this).val();
if (username.length > 0) {
$('#Indicator').removeClass('hide');
$.ajax({
url: '/api/verifyusername',
method: 'GET',
data: { username: username }
}).done(function(response) {
$('#Indicator').addClass('hide');
var indicatorId = response.IsAvailable ? 'Ok' : 'Error';
var indicatorText = response.IsAvailable ? ' is available!' :
' has been taken, sorry!';
$('#Indicator' + indicatorId).removeClass('hide');
$('#IndicatorLabel').removeClass('hide');
$('#IndicatorLabel').html(response.OriginalUsername + indicatorText);
});
}
});
}(jQuery))
</script>
We display the original username
again. This is just so you can see that it’s made the full round-trip to the API and back. We hide all the indicators before we call the API each time. Then we show the one we want, based on what comes back.
Can you think of an example you’d like me to explain end-to-end? Let me know in the comments!
View the original article.