Model-view-controller Feb 21 2006
As I’ve been playing with Ruby, and Ruby on Rails recently, I’ve started to realise that the MVC pattern is the easiest way to work when writing web apps. The level of abstraction that it brings is brilliant, and simplifies things ten-fold. So I decided to write a nice little controller mechanism for .Net/Mono. It’s available in the xFramework repo, under the xFramework.Common.Web library. I plan to add other stuff in here in time (useful base classes for modules/pages, controls perhaps), but for now, this just deals with controllers.
The idea is simple, you simply pass all control of an app to the xFramework ControllerHandler - you can do this in your web.config. Inside the “system.web” node, under the “httpHandlers” section, add a new entry pointing to the xFramework ControllerHandler. It’s full type is “xFramework.Common.Web.Structure.Controllers.ControllerHandler,xFramework.Common.Web”. You can limit the controllers to being accessed at certain locations, or certain tiers of your web app, with the path attribute, or you can use “*” to redirect all requests to the controller handler. Likewise, you can limit the controller handler to only accepting GET or POST methods, or again, use “*” to handle all requests. For this example, we’ll stick with the catch-all.
You can then simply write an IController implementation, and so long as it resides in a library within the bin path of the web app, it’ll be available for calling. There is currently a problem with writing controllers in the App_Code area of .Net 2.0 web projects - they are compiled on the fly, which makes it harder for the reflection loader to pick up the controller types. Easiest solution is to define them all in a separate class library, and then reference that library from within the web site.
Back to our example - there are a few things that an IController must implement. Get/set properties are required for the string Action property (which contains the string name of the action called when a request is made), also, a get/set property for an HttpContext is needed - this allows direct access to the context that the HttpHandler had when it raised a request to a controller. A get accessor on a property called “Name” must be present, and this is a pretty more important one - the name here defines that the controller will “answer on”. More in a second. A get/set string[] property for holding the parameters passed to an action should also be present, and finally, a get property named “UnhandledAction” should be defined. This is a delegate, and should either return an “UnhandledActionHandler” pointing to a method that will be called when no matching action can be found for a request (useful for catch-all’s on a controller etc), or simply define this property as returning null to have the application exception when no matching action is found instead.
In terms of defining the actions on a controller that can be called and requested - simply define a public method, and your away! Any public method on the controller will be accessible - so long as the parameters passed in match those of the method, then it’ll be called. Simple type conversion (as all parameters on an http request start life as a string) will take place, and so it is incredibly easy to create simple controllers.
Ok enough waffle - let’s define a simple controller:using System;
using System.Web;
using xFramework.Common.Web.Structure.Controllers;
namespace Controllers
{
public class MyController : IController
{
#region Private Variables
private string _action;
private HttpContext _context;
private string[] _parameters;
#endregion
#region IController Members
public string Action
{
get { return _action; }
set { _action = value; }
}
public HttpContext Context
{
get { return _context; }
set { _context = value; }
}
public string[] Parameters
{
get { return _parameters; }
set { _parameters = value; }
}
public string Name
{
get { return "mycontroller"; }
}
#endregion
#region Actions
public void Add(int i1, int i2)
{
int result = i1 + i2;
Context.Response.Write(result.ToString());
}
#endregion
}
}
So this is fairly simple, and in most web applications it’ll be easy enough to just define a BaseController (taking care of those boring action/parameters properties), and then have each individual controller simply defining their own action methods. But in this case, running this on a web app, and browsing to http://localhost/mywebapp/mycontroller/Add/½ will result in “3” being output. Maybe I should mention the url format - like I mentioned earlier, the name is used to define what the controller will “answer on”, and how we can call it. Next up the action name, and after that, parameters (if any). So it breaks down like this:
http://[host]/[web app]/[controller name]/[action name]/[parameters]
If we were to add a new method to the above controller example:public void Version()
{
Context.Response.Write("1.0.0.0");
}
then we could access this by browsing to http://localhost/mywebapp/mycontroller/Version. This would output “1.0.0.0”.
As you can see, this is really easy to use, and works fairly well. The API is still a moving target (the UnhandledAction business still isn’t in svn, should be next few days) however it’s a nice way to define endpoints for web app data - for example, all these controllers could return Xml - tie this up with some nice Xsl transforms, and you’ve got a web site using the MVC pattern, that’s well defined, and easy to understand.
I’m currently playing around with an app using all this, so expect bug fixes etc to follow - but if anyone has comments, or indeed patches/improvements, or as always, anything they want to contribute to xFramework, do let me know.
Technorati Tags: el, eldiablo, code, c#, dotnet, mono, mvc, controllers, model-view-controller, xframework