I created this MVC application as a simple demonstration of some of techniques you'll find in larger applications: dependency injection, unit testing, attribute routing, asset minification, unobtrusive jQuery, and validation.
3rd party dependencies aren't included in source control. To get this to build, after opening it in Visual Studio, you will want to right-click on the solution and select 'Enable NuGet Package Restore'
App_Start/AttributeRoutingConfig.cs
This file is auto-generated by Attribute Routing when it is added to your project. Normally routes would be defined in a long single file like this:
routes.MapRoute(
"MyCoolPage", // Route name
"my/cool/page", // URL with parameters
new { controller = "MyController", action = "CoolAction" } // Parameter defaults
);
Instead you can more cleanly do this in the controller, which helps with maintainability:
public class MyController : Controller
{
[GET("my/cool/page")]
public ActionResult CoolAction()
{
// stuff
}
}
App_Start/NinjectWebCommon.cs
This file is auto-generated by Ninject when it is added to your project. Ninject takes care of dependency injection, so your code is easier to unit test. You will want to modify RegisterServices to associate your service interfaces with their main implementations, and Ninject will take care of the rest.
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IMyService>().To<MyService>();
}
CurrencyConversion/Content
The typical location where you store style sheets and associated images.
Controllers/CurrencyController.cs
Controllers are the main part of your MVC application. They take the data from a database or a web service, modify or combine other data appropriately, create a view model, and send it to the view. Or the inverse, if you're working with data posted back from the user.
On the top of this controller you will notice:
public CurrencyController(ICurrencyService currencyService)
You may be wondering if a controller is instantiated by the MVC framework, how it gets an instance of the CurrencyService. Ninject takes care of this automatically, based on the mappings you setup in App_Start/NinjectWebCommon.cs. If for some reason or another you wouldn't want to use a dependency injection framework, you could use this altnernative, which would still easily allow for unit testing:
private ICurrencyService _currencyService;
protected internal ICurrencyService CurrencyService
{
get { return _currencyService = _currencyService ?? new CurrencyService(); }
set { _currencyService = value; }
}
Next you'll see the Index() action, which is pretty self explanitory. We first use Attribute Routing to map the root directory to this action. Inside the function, we hit the service to get the list of available currencies. We create a new instance of the view model, pass in the currencies, setup some defaults, and pass it to the view. You may be wondering how it knows what view to display. By default, calling View() will look for /Views/[Controller]/[Action].cshtml
The Convert() action takes in the posted back form. Since we created the form elements using MVC's HTML helpers in the view, MVC automatically mapped the values back to our view model. We do some server side validation, if the user tried to thwart it on the client side, then do the calculation and return the value back to the user.
Extensions/DictionaryExtensions.cs
This file is an example of an extension method. An extension method is similar to a category in objective-c. It allows you to add a method to a class that isn't your own. In this instance, we're adding ToQueryString() to a dictionary of strings to create a query string for a URL. It is namespaced to the same namespace as the class we are adding on to for intellisense reasons, with the downside of the posibility of having conflicts with other extension methods.
var parameters = new Dictionary<string, string>();
parameters.Add("app_id", "blabla");
string queryString = parameters.ToQueryString();
Will generate:
?app_id=blabla
Models/Currency/IndexModel.cs
This is the model we use for the index action on the currency controller. Typically a view model will map one-for-one with what is needed in the view. For example, if you are formatting a date to be displayed on the view, your view model should have a string where you will store the formatted date. You want to have minimal logic in the view. You also don't want to pass a raw database or service model to the view.
You'll notice the validation rules are also on the model in the form of attributes. They are both used for jQuery Validate (generated by the html helpers in the form of HTML 5 attributes) and for MVC's server-side validation.
Properties/AssemblyInfo.cs
This file holds a bunch of metadata about your application such as the name, copyright information, and the version. The one line I want to point out is:
[assembly: InternalsVisibleTo("CurrencyConversion.Tests")]
Which makes all properties and methods with the 'internal' access modifier available to the Test project.
Scripts
Where scripts go.
Services/CurrencyService.cs
This is the service that accesses Open Exchange Rates. It accesses the service and parses the result with Newtonsoft Json which is generally considered better performing than other JSON frameworks.
The WebClient class is part of the base .NET 4.5 framework.
Services/ICurrencyService.cs
The interface for the above service, used for dependency injection and unit testing.
Services/CurrencyServiceSettings.cs
This defines a custom XML setting for the service, which is used in the web.config file.
This setting stores the Open Exchange Rates API url and your key. It is typically good
practice to store user configuration settings in the web.config file, because you can
leverage many tools to help modify this file depending on the environment you are on (dev, beta, production, etc).
Views/Web.config
Most of this file is auto-generated. The only part you might really modify is the section which will allow you to reference classes in views without the need for a @using line.
Shared/Layout.cshtml
There is nothing particuarly special about this view that makes it a layout, other than the fact that it is referenced as one in other views. At the top you'll see that uses SquishIt for the combination and minification of assets. Whether or not the assets gets combined depends on this line in the web.config:
<compilation debug="true" targetFramework="4.5" />
You should also notice:
@RenderBody()
This is the placeholder for where the other views' contents will go. If you need more than one place to put content in a template, you can use sections, which are placed in the layout using the following syntax:
@RenderSection("SectionName")
Then in the view you would do:
@section SectionName {
<b>bla bla stuff here</b>
}
Views/Currency/Index.cshtml
This is the view used for the Index action in the Currency controller. It uses the Razor view engine. At the top, we define the layout, and set the title of the page:
@{
ViewBag.Title = "Index";
Layout = "~/Views/Shared/Layout.cshtml";
}
Next, we use a HTML helper to display a list of validation errors, if the view is loaded with a model that already has errors:
@Html.ValidationSummary();
In the FORM tag, you'll first notice that we generate the URL server-side. Always use the Url.Action helpers to generate a URL based on a controller and action! This allows you to change the routes of your pages without worrying about updating all your views:
@Url.Action("Convert")
You will also notice in the FORM tag the HTML5 data attributes, which ties into the Unobtrusive jQuery Ajax plugin, arguably making the markup easier to understand and reduces repetative JavaScript.
The other HTML helpers like DropDownFor and TextBoxFor ensure that the markup generated will properly post the data back to MVC in a way that it will properly bind with the View Model that made the page in the first place. They will also generate the appropriate data attributes used with the Unobtrusive jQuery Validation plugin, again, which reducuces the JavaScript required.
@Html.DropDownListFor(m => m.SourceCurrency, Model.AvailableCurrencies)
Global.asax.cs
This file has functions that are called for the lifecycle of the application. The most common is Application_Start() where things like cache mechanisms might be setup.
In a default MVC configuration, you will see the routes being built here, but again we are instead using a route attribute plugin instead.
You will also notice App_Start/AttributeRoutingConfig.cs and App_Start/NinjectWebCommon.cs are not referenced here, even though both of those files are run at startup. That is because they are using WebActivator to inject themselves into the website lifecycle.
Web.config
Think of this file as IIS's equivalent to Apache's .htaccess. It contains all the configuration settings for the application.
We use the custom setting we setup in Services/CurrencyServiceSettings.cs here:
<configSections>
<section name="CurrencyServiceSettings" type="CurrencyConversion.Services.CurrencyServiceSettings, CurrencyConversion" />
</configSections>
<CurrencyServiceSettings key="YOUR_API_KEY_HERE" />
You will want to sign up for Open Exchange Rates and put your API key there.
You'll notice there are also a debug and release versions. This allows you to have different values for attributes based on the current build configuration.
CurrencyConversion.Tests
Unit tests are held in a separate project. We utalize NUnit for the testing framework and Rhino Mocks for object mocking.
CurrencyConversion.Tests/Services/CurrencyServiceTest.cs
This file includes an example of mocking with Rhino Mocks to test parts of a class. There are three types of mocking available:
- StrictMock - Only methods explicitly recorded are valid
- DyanmicMock - Methods not setup return null or void
- PartialMock - Non-mocked functions will fall back to the original
In the case of GetCurrencies_Should_Parse_Object_As_Dictionary(), we want to test GetCurrencies in the CurrencyService class while mocking out GetJsonFromWebService in the CurrencyService class. Therefore, we create a PartialMock.
After setting up our mocks, we want to 'play' them by calling
mocks.ReplayAll()
Next, we make our assertions, and verify they all passed using
mocks.VerifyAll()
You can also test that exceptions are thrown, as demonstrated in GetExchangeRate_Error_Should_Throw_Proper_Exception()
CurrencyConversionTests/Controllers/CurrencyControllerTest.cs
This demonstrates stubbing. Stubbing is where you're not partially testing a class, but rather testing one class that depends on another. In our case, we just want to test the CurrencyController, so we pass in a stub CurrencyService to ensure we aren't hitting any of it's logic, and pre-setup to return the values that we want and can expect.