I recently decided to pull down
MVCommand, which is a front controller implementation built on top of ASP.NET MVC that uses individual Command objects (or a set of Command objects) to process requests. It was created by an old co-worker of mine,
Erik Peterson. You can read more about his implementation
here and
here.
I was interested in the project because I am of the opinion that a controller handling multiple actions seems like its doing too much and this solution addresses that issue. It also seems like a shorter tail solution than picking up FubuMVC, while accomplishing many of the same things that FubuMVC is trying to accomplish.
I wanted to see how easy it was to use MVCommand and I was impressed at how simple it was to get it going. The app I created is about as simple as simple gets... but I really just wanted to see how much effort was involved in getting something set up to use MVCommand.
Getting MVCommand:
The first thing I did was pull down MVCommand from Erik's github profile. There is a version specific to ASP.NET MVC 2, so I grabbed
that. But there's also a version that plays with the 1st version of ASP.NET MVC, so if you're still using that version, you can download the MVCommand trunk and you'll be set. Compile it into a DLL, or just open the solution and follow the steps below.
Creating A New Project:
The next thing I did was create a new ASP.NET MVC, since MVCommand still uses the same project structure - views are stored in the Views folder, Models in the Model folder, etc. Add a new reference either to MVCommand project if it's in your solution, or the DLL you compiled from the previous step.
Setting Up Your Front Controller:
Instead of having many controllers that relate to different routes in your MVC application, MVCommand handles all requests through a single controller. That controller determines which command, or set of commands, to run based on the route and executes them.
Setting up the controller used to handle this is a cinch.
1: [HandleError]
2: public class ConfigController : CommandController
3: {
4: public override Type[] CommandTypes
5: {
6: get
7: {
8: var temp = typeof (ConfigController).Assembly.GetTypes().Where(x => typeof (ICommand).IsAssignableFrom(x) && !x.IsInterface).ToArray();
9: return temp;
10: }
11: }
12:
13: public override Type BindableCommandType
14: {
15: get { throw new NotImplementedException(); }
16: }
17: }
CommandController is an abstract class provided by MVCommand. It forces you to implement two properties - CommandTypes and BindableCommandType. BindableCommandType is too advanced for this post, but the CommandTypes array is just a collection of all the commands in your project. You can set this up however you like, whether it be manually adding them, using an IoC tool, or just running through the assembly types and returning all of the Command objects, as I did above. In the end, you just want to have a list of all the Types in the assembly that are Commands.
Setting Up Your Front Controller Factory:
After you've created your front controller, you have to create a new controller factory - the class responsible for returning the correct controller. Again, since ASP.NET MVC's controller factory will return whatever controller corresponds to the request, we need to override that behavior and have our default controller used for every request.
Again, this is pretty simple.
1: public class ConfigControllerFactory : CommandControllerFactory
2: {
3: public override IController CreateController(RequestContext requestContext, string controllerName)
4: {
5: var commandController = new ConfigController();
6: return commandController;
7: }
8: }
CommandControllerFactory is an abstract class in the MVCommand project. Create your own controller factory class and inherit CommandControllerFactory. The overridden method implementation should just create your front controller.
In order to let ASP.NET MVC know that you want to use your custom controller factory instead of the default one, you have to add this line to your global.asax file in the Application_Start method:
1: ControllerBuilder.Current.SetControllerFactory(typeof(ConfigControllerFactory));
Setting up an IoC Container for Service Location:
MVCommand uses Microsoft Patterns and Practices Service Location to create instances of the commands it executes. The MS Service Locator is an abstract service location tool which lets you use any IoC container and service locator in your code without hardcoding references to the IoC in your code.
I used StructureMap as my IoC container in my application. I'm not going to get into setting up the StructureMap container because you find that elsewhere. But once you have the container, you do have to tell Microsoft's Service Locator how to use it to, well, locate your services :)
To do that, I created a class called StructureMapServiceLocator that overrides ServiceLocatorImplBase. This base class is what MS Service Locator uses to find your classes, so you basically have to fill in the overriden methods with calls to your StructureMap container that retrieves instances of the requested type. It looks like this:
1: public class StructureMapServiceLocator : ServiceLocatorImplBase
2: {
3: private readonly IContainer _container;
4:
5: public StructureMapServiceLocator(IContainer container)
6: {
7: _container = container;
8: }
9:
10: public IContainer Container { get { return _container; } }
11:
12: protected override object DoGetInstance(Type serviceType, string key)
13: {
14: return string.IsNullOrEmpty(key)
15: ? _container.GetInstance(serviceType)
16: : _container.GetInstance(serviceType, key);
17: }
18:
19: protected override IEnumerable<object> DoGetAllInstances(Type serviceType)
20: {
21: return _container.GetAllInstances(serviceType).Cast<object>().AsEnumerable();
22: }
23:
24: public override TService GetInstance<TService>()
25: {
26: return _container.GetInstance<TService>();
27: }
28:
29: public override TService GetInstance<TService>(string key)
30: {
31: return _container.GetInstance<TService>(key);
32: }
33:
34: public override IEnumerable<TService> GetAllInstances<TService>()
35: {
36: return _container.GetAllInstances<TService>();
37: }
38: }
And just like with your overriden controller factory, you have to tell the MS Service Locator to use your StructureMap service locator in the global.asax file:
1: var serviceLocator = new StructureMapServiceLocator(container);
2: ServiceLocator.SetLocatorProvider(() => serviceLocator);
Creating Your First Command:
By default, MVCommand runs all of the commands in a namespace that relates to your route. This behavior can be overriden by providing a dictionary that maps commands to a route, but that's beyond the scope of this post.
The main route is below:
1: routes.MapRoute(
2: "Default", // Route name
3: "{context}/{event}/{id}", // URL with parameters
4: new {controller = "Command", action = "DefaultAction", id = ""} // Parameter defaults
5: );
(note that the second token of the url has been renamed from 'action' to 'event' - you will need to rename that token as well)
In the case of this route, MVCommand will run all the commands where the namespace ends with 'Context.Event'. So for example, in my example, I called this url:
http://localhost:1162/home/helloworld and the command whose namespace ends with
*whatever*.Home.HelloWorld. If there's multiple commands in that namespace, it'll run them all.
For my insanely simple example, I created a new Command simply called Command2 with hard-coded return value of my name and age.
1: namespace MVCommand.HelloWorld.Commands.Home.HelloWorld
2: {
3: public class Command2 : ICommand
4: {
5: public object Execute()
6: {
7: return new HelloWorldModel {Age = 31, Person = "Dan" };
8: }
9: }
10: }
(note that the namespace ends with Home.HelloWorld')
This class implements the ICommand interface that is part of the MVCommand framework. This is insanely simple, but you could do any processing necessary in this class, whether it be hitting a database or whatever the case may be.
The returned object is set in the ViewData dictionary.
Using The Returned Model In The View:
The last piece of the puzzle is getting the data that was returned from the Command and using it in your View. I created a HelloWorld.aspx view and made it inherit from an MVCommand helper class ViewBasePage<T> where T is the model. This just creates a property named Model with the object that is returned from your command.
1: <%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="MVCommand.Views.ViewBasePage<MVCommand.HelloWorld.Models.HelloWorldModel>" %>
2:
3: <asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
4: HelloWorld
5: </asp:Content>
6:
7: <asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
8:
9: <h2>HelloWorld</h2>
10:
11: <p>
12: Hello, <%: Model.Person%>.<br />
13: You are <%: Model.Age %> years old.<br />
14: </p>
15:
16:
17: </asp:Content>
Wrapping It Up:
That's about it. As you can see, it's pretty easy to get up and running with MVCommand. Just set up a front controller and new controller factory to create instances of that controller. Then, set up your IoC container and service location. Then, start creating commands and views and go to town!
Hopefully that is enough to get you started with MVCommand. Download it and start playing around.
I hope to do more posts in the future about MVCommand. I know there are other things to go over, such as using the Command dictionary instead of namespacing to determine which commands to run for a request. And how to get and use form data and querystring values in your Commands. Look for those posts at a later date.
Til then... don't hesitate to leave comments with questions, or if you noticed I royally screwed something up. It wouldn't surprise me if I missed something, mis-typed something or otherwise had some sort of brain fart while writing this up. So thanks in advance for the feedback.