Rails-style controllers for ASP.NET
- 📅
- 📝 610 words
- 🕙 3 minutes
- 📦 .NET
- 🏷️ ASP.NET, C#
- 💬 1 response
Rob Conery has been putting together some great screen casts on SubSonic and his latest on generating controllers pointed out that ASP.NET doesn’t support the Rails-style http://site//controller/method style of execution.
This got me quite excited and I’ve put together a proof-of-concept web project that demonstrates mapping the path to controller methods using an IHttpHandler
and reflection.
How it works
It registers the ControllerHttpHandler
via the web.config
:
<httpHandlers>
<add path="/ctl/\*/\*" verb="POST,GET,HEAD" type="ControllerHttpHandler" />
</httpHandlers>
There is a very basic Controller
abstract base class that just provides a simple way of accessing the context for dealing with request/response for now.
public abstract class Controller
{
protected System.Web.HttpContext context;
internal Controller(System.Web.HttpContext context) {
this.context = context;
}
}
We then have a test controller or two that implement from this with a couple of methods and the required constructor:
public class TestController : Controller
{
public TestController(System.Web.HttpContext context) : base(context) { }
public void Index() {
context.Response.Write("This is the index");
}
public void Welcome() {
context.Response.Write("Welcome to the TestController");
}
}
Finally the magic that joins them up is the ControllerHttpHandler
:
using System;
using System.Web;
using System.Reflection;
public class ControllerHttpHandler : IHttpHandler
{
public void ProcessRequest(HttpContext context) {
string[] parts = context.Request.Path.Split('/');
if (parts.Length < 4) {
context.Response.Write("No controller & member specified");
return;
}
string controllerName = parts[2];
string methodName = parts[3];
Type potentialController = Type.GetType(controllerName);
if (potentialController != null && potentialController.IsClass && potentialController.IsSubclassOf(typeof(Controller))) {
MethodInfo potentialMethod = potentialController.GetMethod(methodName);
if (potentialMethod != null) {
Controller controller = (Controller) Activator.CreateInstance(potentialController, context);
potentialMethod.Invoke(controller, null);
}
else
context.Response.Write(String.Format("Method '{0}' not found on controller '{1}'", methodName, controllerName));
}
else
context.Response.Write(String.Format("Controller '{0}' not found", controllerName));
}
public bool IsReusable {
get { return false; }
}
}
That’s it!
Limitations
The controllers and methods are mapped at run-time using reflection. This would probably be too slow for production. Also it currently has to be in a top-level folder because I can’t figure out how to pass the HTTP request back to ASP.NET to try with the rest of the stack if we don’t have a matching controller/method.
One option might be to have no httpHandlers in the web.config and add the exact controller/method maps at build or run-time. This solves both the top-level problem and potentially the speed.
Another option to address just the speed of reflection would be to cache the path/method strings to the actual method and type so the only reflection would be the Activator.CreateInstance
. If that is slow then we could look at pooling the controller instances themselves.
Going forward
Parameters for a method could be extracted and parsed from the query-string — they are currently ignored.
Response is raw output — we could do something very similar to rhtml.
I’m going to chat things over with the Subsonic team and see if we can come up with anything from here.
[)amien
1 response to Rails-style controllers for ASP.NET
Damien,
This is good stuff. I would like to see how far you go with this.
In my opinion there has to be an easier way to Rails-like features on .NET than with MonoRail.
I don’t think the SubSonic solution is much in the way of MVC unless the view is the new controller.
Keep it up.
-Rob