Golf Tracker - Presentation Layer and Unity
In this episode I explain many aspects to what I need to do to prepare the presentation layer to receive and process data from the service layer and other sources.Dependency Injection with Unity
There are many Inversion of Control (IoC) containers to help you with dependency injection. In this application I've chose to use the Microsoft Practices framework called Unity. Others include:- Ninject
- StructureMap
- Autofac
- etc
- Microsoft.Practices.EnterpriseLibrary.Common
- Microsoft.Practices.ObjectBuilder2
- Microsoft.Practices.Unity - version 1.2
Here's what the project references look like.
Unity Bootstrapper
To encapsulate the Unity functionality even further, and to make it easier and neater to refactor in the future, I've created a Bootstrapper class where all the mapping of concrete classes to interfaces will be handled. Here's a look at the Bootstrapper class.01.using System.Web.Security;02.using GolfTracker.Api;03.using GolfTracker.DataObjects.Interfaces;04.using GolfTracker.DataObjects.Repositories;05.using GolfTracker.Mvc.Web.Models;06.using GolfTracker.Services;07.using GolfTracker.Services.Interfaces;08.using Microsoft.Practices.Unity;09.using Unity.Library.Mvc2;10. 11.namespace GolfTracker.Mvc.Web.Unity12.{13. public class Bootstrapper14. {15. private static IUnityContainer _container;16. 17. private static void Configure(IUnityContainer container)18. {19. container20. .RegisterType<ICourseService, CourseService>()21. .RegisterType<ICourseRepository, CourseRepository>()22. .RegisterType<IPlayerService, PlayerService>()23. .RegisterType<IPlayerRepository, PlayerRepository>()24. .RegisterType<IRoundRepository, RoundRepository>()25. .RegisterType<ITeeRepository, TeeRepository>()26. .RegisterType<ITeeService, TeeService>()27. .RegisterType<IRoundService, RoundService>()28. .RegisterType<IRoundManager, RoundManager>()29. .RegisterType<IFormsAuthenticationService, FormsAuthenticationService>()30. .RegisterInstance<IMembershipService>(new AccountMembershipService(Membership.Provider))31. .RegisterType<IRoleService, RoleService>();32. 33. UnityFactoryBuilder.Init(container);34. }35. 36. public static void Configure()37. {38. if (_container == null)39. {40. _container = new UnityContainer();41. }42. Configure(_container);43. }44. }45.}(NOTE: this is the final implementation of the bootstrapper class with all the class mappings. The video only shows those classes that are completed at this point.)
To make this work nicely, I need to include my Unity library which is called on line 33. The UnityFactoryBuilder.Init(container) method creates the controller factory class that handles the resolving of the concrete classes to the interfaces.
Once this class is built, I simply reference it in the Global.asax class and I'm good to go. Here's how it is used.
01.using System.Web.Mvc;02.using System.Web.Routing;03. 04.namespace GolfTracker.Mvc.Web05.{06. // Note: For instructions on enabling IIS6 or IIS7 classic mode, 07. // visit http://go.microsoft.com/?LinkId=939480108. 09. public class MvcApplication : System.Web.HttpApplication10. {11. public static void RegisterRoutes(RouteCollection routes)12. {13. routes.IgnoreRoute("{resource}.axd/{*pathInfo}");14. 15. routes.MapRoute("GetCourses", "Course/GetCourses/{state}/{startsWith}", new { controller = "Course", action = "GetCourses", state = "", startsWith = "" },16. null, new string[] { "GolfTracker.Mvc.Web.Controllers" });17. 18. routes.MapRoute(19. "Default", // Route name20. "{controller}/{action}/{id}", // URL with parameters21. new { controller = "Home", 22. action = "Index", 23. id = UrlParameter.Optional }, // Parameter defaults24. null,25. new string[] { "GolfTracker.Mvc.Web.Controllers" }26. 27. );28. }29. 30. protected void Application_Start()31. {32. GolfTracker.Mvc.Web.Unity.Bootstrapper.Configure();33. 34. AreaRegistration.RegisterAllAreas();35. 36. RegisterRoutes(RouteTable.Routes);37. }38. }39.}Line 32 contains the call to the Bootstrapper class. That's all that's needed to configure Unity using my library helper. This helps make the Global.asax cleaner so it doesn't contain all the mappings.
Also, the Bootstrapper class can also be in it's own project for easier deployment, but some other refactoring to your application would need to be done, but it is an option.
Setup your application
Now I want to finish setting up my application so I can quickly add functionality to it. In order to do that I've borrowed some ideas from the KiGG application. What these ideas help me do is implement a way of pulling common data that will be needed in either every controller call, or every view.MVC is built on top of the concept of "convention over configuration". This means that specific conventions drive the framework, such as the name of a controller is also the name of the folder for the views it will display.
I also have incorporated various conventions in the way I build my MVC applications. One of them is the use of view models for 99% of all view presentations. In other words, I pass a ViewModel or ViewData class of some sort to the view and NOT simply the business object. This allows me to easily pass various pieces of data that may be needed for that view, such as data for dropdown boxes, or any other related information.
Following this thought, I've built my controllers to use custom ViewData classes for each business object that contains both a single business object, and a collection of business objects.
using System.Collections.Generic;using GolfTracker.BusinessObjects;namespace GolfTracker.Mvc.Web.ViewData{ public class CourseViewData : BaseViewData { public Course Course { get; set; } public List<Course> CourseList { get; set; } public double Avg { get; set; } }}You'll notice that this ViewData class (as does all others) inherits from a BaseViewData class. This class can contain any common data that you want to pass up to the controller, and/or to the View. More about this in a moment.
Side Note: another convention I use, primarily since I use a code-generator to create much of my CRUD operations for my application, is the way I handle the property name for collections. In the example above the CourseViewData class has two methods for the Course business object, a single Course property and a List of Courses. For the collections (List<Course>) my convention is to append the word "List" to the name of the business object. This works out much better than just trying to append an "s", making it Courses. This tends to muck up generated code from the code-generator unless you have an extremely intelligent Pluralizer. Because while this will work for words like Course, it wouldn't work for words like News. What's the plural of News, Newses?? So by having the naming convention for pluralizing names, I append the work "List". (Another Note: this really only works if your naming convention for the business objects to begin with are singular.)
By creating a few different classes I'll be going from something like this in my controllers:
1.public ActionResult Index()2.{3. CourseViewData viewData = new CourseViewData();4. viewData.CourseList = service.GetAll();5. 6. return View(viewData);7.}... to this ...
1.public ActionResult Index()2.{3. CourseViewData viewData = ViewDataFactory.CreateBaseViewData<CourseViewData>("Course List");4. viewData.CourseList = service.GetAll();5. 6. return View(viewData);7.}Notice that I'm now calling a ViewDataFactory static class instead of simply creating a new instance of the view data class. What this does, under the covers, is it pulls data from various sources, sets a base class with properties and sends them back up to the controller and eventually the view to be used as necessary.
The BaseViewData class is simply an abstract class that contains properties that will be populated by another class.
namespace GolfTracker.Mvc.Web.ViewData{ public abstract class BaseViewData { public string SiteTitle { get; set; } public string RootUrl { get; set; } public string MetaKeywords { get; set; } public string MetaDescription { get; set; } public string PageTitle { get; set; } public string IPAddress { get; set; } }}Here's a look at the folder structure for the ViewData folder.
It's the BaseViewDataBuilder class that will populate the BaseViewData classes properties.
namespace GolfTracker.Mvc.Web.ViewData{ public class BaseViewDataBuilder { public static T CreateViewData<T>(string pageTitle) where T : BaseViewData, new() { T viewData = new T { SiteTitle = ConfigSettings.SiteTitle, RootUrl = Common.GetSiteRoot(), MetaKeywords = ConfigSettings.MetaKeywords, MetaDescription = ConfigSettings.MetaDescription, PageTitle = pageTitle }; return viewData; } }}In this example, the values that are setting the properties are coming from the appSettings config file, but they can easily come from a database.
By using this methodology to create my ViewData or view model for the View, I am passing essential data up the stack without any repetitive work. This also makes it easy to refactor what values I need to include by simply modifying the BaseViewData properties and then setting them in the BaseViewDataBuilder class.
Configuration
Another thing I do to setup my application is to extract out the appSettings and connectionStrings sections of the web.config. By doing this it enables me to have settings and connection strings for development and a different set of settings for production. So whenever I need to publish a new version of the application to the production server, I won't be overwriting any production settings with those for development.Here's how I enable this in the web.config.
<appSettings configSource="settings.config"/><connectionStrings configSource="sql.config"/>Here's a look at the files in the root of the project.
Then I can use a class to get the appSettings properties to use in the BaseViewData class.
Displaying the UI
Once all the action methods are created in the controller, it's easy to create the views. But as you would imagine I have a certain convention for my basic CRUD views.When it's possible I like to have a UserControl (Partial View) used to contain the actual form that is used for both the Create and Edit views.
Here's the CourseFormControl partial view.
<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<GolfTracker.BusinessObjects.Course>" %> <% using (Html.BeginForm()) {%> <%= Html.ValidationSummary(true) %> <fieldset> <div class="editor-label"> <%= Html.LabelFor(model => model.CourseName) %> </div> <div class="editor-field"> <%= Html.TextBoxFor(model => model.CourseName, new { @class = "text" })%> <%= Html.ValidationMessageFor(model => model.CourseName) %> </div> <div class="editor-label"> <%= Html.LabelFor(model => model.State) %> </div> <div class="editor-field"> <%= Html.TextAreaFor(model => model.State, 10, 40, null) %> <%= Html.ValidationMessageFor(model => model.State) %> </div> <%= Html.HiddenFor(model => model.rowversion) %> <p> <input type="submit" value="Save" /> </p> </fieldset> <% } %> <div> <%= Html.ActionLink("Back to List", "Index") %> </div>And here is how it's used in the Create view.
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/GolfTrack.Master" Inherits="System.Web.Mvc.ViewPage<GolfTracker.Mvc.Web.ViewData.CourseViewData>" %><asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> <%= Model.SiteTitle %> - <%= Model.PageTitle %></asp:Content><asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2><%= Model.PageTitle %></h2> <% Html.RenderPartial("CourseFormControl", Model.Course); %></asp:Content><asp:Content ID="Content3" ContentPlaceHolderID="HeadContent" runat="server"></asp:Content>And the Edit view is nearly identical.
<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/GolfTrack.Master" Inherits="System.Web.Mvc.ViewPage<GolfTracker.Mvc.Web.ViewData.CourseViewData>" %><asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> <%= Model.SiteTitle %> - <%= Model.PageTitle %></asp:Content><asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> <h2><%= Model.PageTitle %></h2> <% Html.RenderPartial("CourseFormControl", Model.Course); %></asp:Content><asp:Content ID="Content3" ContentPlaceHolderID="HeadContent" runat="server"></asp:Content>For me, this makes it easier to make common modifications to the form and have it reflected on both views. DRY!
Notice that in both cases, the RenderPartial HTML extension, passes in a Business Objects Course, instead of the entire CourseViewData class. This is what the partial view is expecting, while the primary view is expecting other information such as the PageTitle and SiteTitle.
Conclusion
Now that the MVC application is setup (for the most part), the next thing I want to do is use some tooling that will help me generate controllers and views that are completely rendered exactly the way I need them for this application, instead of manually creating them every time.In the next couple of episodes I'll demonstrate how to use and customize T4 templates to build custom controllers and views.
Stay tuned.

