My first foray into ASP.NET MVC

I have recently begun the .NET MVC journey, and I see recurring “buzzwords”: Inversion of Control (IoC) and Dependency Injection (DI). If you are at the same point in your MVC journey as me, you may be wondering what the heck these things are?!?! They have certainly caused me a bit of confusion. I have yet to implement DI, but I have decided to stick with the MS stack and try out Unity.

I decided I’d give this whole thing another shot tonight. I recently built my first little MVC application – a guestbook. Remember those from 1996? I thought it would be something a bit different and I knew it would be pretty simple (the database is two tables, 1 of which I am really not using right now). I also brought it into the 21st century with a little Google mappage to show where the guestbook signer is located on our big, blue marble. I’ve got it mostly working and I’ll get it up online as soon as I get the membership stuff locked down.

I want to “do it the right way”, so I want to try out the DI stuff. I have been searching for a good starter post that would break down the whole IoC/DI stuff into small, digestible chunks that my overworked brain could handle. I finally found .NET HITMAN’s post that breaks down the history behind Unity, why DI is a good thing, and how to add references to the Unity DLL’s to your project, which is exactly what I was in need of. Since this is an ASP.NET MVC app, I also found Shiju Varghese’s post that explains how to add DI to the NerdDinner project. Since I “followed along” with the NerdDinner app while creating my Guestbook app, I found the post extremely helpful and easy to follow.

When I started off with my MVC project, I went with SubSonic 3.0 for data access. I am a big fan of SubSonic and I use 2.1 at work, so I was really interested to see what 3.0 had to offer and I knew that with Rob’s focus on MVC that 3.0 would work quite nicely. It also gave me a chance to use some LINQ goodness, which, to this point, I haven’t been able to use extensively. I was quite impressed with the T4 LinqTemplates and overall I think 3.0 is a great new addition to the SubSonic family.

Another thing I keep seeing in MVC apps is repositories. It is my understanding that the repository enables easier unit testing (but I am an .NET MVC n00b, so I could be wrong) since the controller isn’t talking directly to your data access code. So I went about creating a GuestbookRepository class for my controller to talk to.

Now that I am going the whole DI route, it seems I need to first create an interface, then inherit from that interface. So here is the IGuestbookRepository interface:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using MvcApplication.Models;

namespace MvcApplication.Repositories {

    public interface IGuestbookRepository {

        IQueryable<mvc_guestbook> GetAllMessages();
        mvc_Guestbook GetMessage(int id);
        void Add(mvc_Guestbook guestbook);
        void Delete(mvc_Guestbook guestbook);
        void Save(mvc_Guestbook guestbook);
        void Update(mvc_Guestbook guestbook);
    }
}

* NOTE: I don’t really like the mvc_Guestbook references in there (they seem smelly) but I am not sure what I should put there, so this is going to have to do for now until I determine if this is correct or find out what is correct.

Now that I have my interface, I modifed my GuestbookRepository class to inherit from the interface. Here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

using MvcApplication.Models;

namespace MvcApplication.Repositories {

    public class GuestbookRepository : IGuestbookRepository {

        private GuestbookDB db = new GuestbookDB();

        public IQueryable<mvc_guestbook> GetAllMessages() {
            return from guestbook in db.mvc_Guestbooks
                   orderby guestbook.MessageDate descending
                   select guestbook;
        }

        public mvc_Guestbook GetMessage(int id) {
            return db.mvc_Guestbooks.SingleOrDefault(g => g.GuestbookID == id);
        }

        public void Add(mvc_Guestbook guestbook) {
            guestbook.Add();
        }

        public void Delete(mvc_Guestbook guestbook) {
            guestbook.Delete();
        }

        public void Save(mvc_Guestbook guestbook) {
            guestbook.Save();
        }

        public void Update(mvc_Guestbook guestbook) {
            // Save message history
            SaveHistory(guestbook.GuestbookID);
            // Save updated message
            guestbook.Update();
        }

        private void SaveHistory(int id) {
            mvc_Guestbook past = mvc_Guestbook.SingleOrDefault(g => g.GuestbookID == id);
            mvc_GuestbookHistory history = new mvc_GuestbookHistory();
            history.GuestbookID = past.GuestbookID;
            history.Name = past.Name;
            history.Email = past.Email;
            history.Message = past.Message;
            history.Location = past.Location;
            history.Latitude = past.Latitude;
            history.Longitude = past.Longitude;
            history.EditDate = DateTime.Now;
            history.Add();
        }
    }
}

We’ve got all our basics: Get all, get one, create, delete. There are also methods for adding and saving. It seems SubSonic uses .Save() as either add or update, while .Add() and .Update() only work as, well, add or update. Also of note in the Update method is the call to SaveHistory. This is meant to show an update history of the messages, though I am not currently sure if I am going to enable editing. Better to have this in now and not need it, I guess.

Finally, we have the GuestbookController which uses the dependency injection I just set up. Here is the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Ajax;

using MvcApplication.Models;
using MvcApplication.Repositories;

namespace MvcApplication.Controllers {

    public class GuestbookController : Controller {

        private IGuestbookRepository gr;

        public GuestbookController(IGuestbookRepository repository) {
            gr = repository;
        }

        public ActionResult Index(int? page) {
            const int pageSize = 5;
            var guestbook = gr.GetAllMessages();
            var pagedGuestbook = new PaginatedList<mvc_guestbook>(guestbook,
                page ?? 0,
                pageSize);
            return View(pagedGuestbook);
        }

        public ActionResult AjaxIndex(int? page) {
            const int pageSize = 5;
            var guestbook = gr.GetAllMessages();
            var pagedGuestbook = new PaginatedList<mvc_guestbook>(guestbook,
                page ?? 0,
                pageSize);
            return PartialView("Messages", pagedGuestbook);
        }

        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Create() {
            if (Session[Request.ServerVariables["REMOTE_ADDR"].ToString()] != null ? DateTime.Now.Subtract(Convert.ToDateTime(Session[Request.ServerVariables["REMOTE_ADDR"].ToString()])).Minutes >= 5 : true) {
                mvc_Guestbook guestbook = new mvc_Guestbook();
                return View(guestbook);
            } else {
                Session["DELAY"] = (5 + Convert.ToDateTime(Session[Request.ServerVariables["REMOTE_ADDR"].ToString()]).Subtract(DateTime.Now).Minutes).ToString();
                return View("Denied");
            }
        }

        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Create(mvc_Guestbook guestbook) {
            if (ModelState.IsValid) {
                try {
                    guestbook.MessageDate = DateTime.Now;
                    gr.Save(guestbook);
                    Session[Request.ServerVariables["REMOTE_ADDR"].ToString()] = DateTime.Now.ToString();
                    return RedirectToAction("Details", new { id = guestbook.GuestbookID });
                } catch {
                    ModelState.AddModelError("Error", "Error saving message");
                }
            }
            return View();
        }

        [Authorize]
        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Edit(int id) {
            mvc_Guestbook guestbook = gr.GetMessage(id);
            if (guestbook == null) {
                return View("Not Found");
            } else {
                return View(guestbook);
            }
        }

        [Authorize]
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Edit(int id, FormCollection collection) {
            mvc_Guestbook guestbook = gr.GetMessage(id);
            try {
                UpdateModel(guestbook);
                gr.Update(guestbook);
                return RedirectToAction("Details", new { id = guestbook.GuestbookID });
            } catch {
                ModelState.AddModelError("Error", "Error saving message");
                return View(guestbook);
            }
        }

        [Authorize]
        [AcceptVerbs(HttpVerbs.Get)]
        public ActionResult Delete(int id) {
            mvc_Guestbook guestbook = gr.GetMessage(id);
            if (guestbook == null) {
                return View("Not Found");
            } else {
                return View(guestbook);
            }
        }

        [Authorize]
        [AcceptVerbs(HttpVerbs.Post)]
        public ActionResult Delete(int id, string confirmButton) {
            mvc_Guestbook guestbook = gr.GetMessage(id);
            if (guestbook == null) {
                return View("Not Found");
            } else {
                gr.Delete(guestbook);
                return View("Deleted");
            }
        }

        public ActionResult Details(int id) {
            mvc_Guestbook guestbook = gr.GetMessage(id);
            if (guestbook == null) {
                return View("NotFound");
            } else {
                return View(guestbook);
            }
        }
    }
}

For the code to wire this all up, I used Shiju’s example of the HttpContextLifetimeManager class as well as the UnityControllerFactory class. However, since I wasn’t using the NerdDinner code but my own Guestbook, I had to make the following changes to the UnityControllerFactory.Configure method:

public static void Configure() {
    //create new instance of Unity Container
    IUnityContainer container = new UnityContainer();
    //Register dependencies
    container.RegisterType<IFormsAuthentication, FormsAuthenticationService>();
    container.RegisterType<IMembershipService, AccountMembershipService>();
    container.RegisterInstance<MembershipProvider>(Membership.Provider);

    container.RegisterType<IGuestbookRepository, GuestbookRepository>(new HttpContextLifetimeManager<IGuestbookRepository>());
    ControllerBuilder.Current.SetControllerFactory(new UnityControllerFactory(container));
}

Finally, as Shiju illustrates, I added the call to UnityControllerFactory.Configure() into the Application_Start() method in my Globals.asax file and all is well.

All in all, I wouldn’t say I am 100% comfortable with exactly all that is happening in the code, but it is a good start at understanding DI with .NET MVC.