Mercury is a web framework built atop ASP.NET and ASP.NET MVC. It is written primarily in Boo. It provides an internal DSL meant to streamline the process of composing a route-based web application that is not only easier on the eyes and wrists, but is also sustainable and maintainable. Mercury draws its inspiration from the ruby-based Sinatra web framework. That being said, there are a number of non-trivial differences between the two, having much to do with the idiosyncrasies of their respective backing architectures and the fact that Ruby is a dynamic language while Boo is statically typed.
Mercury is released under the terms of the MIT License. It is kept under source control at http://github.com/olsonjeffery/mercury.
A great big thanks to the developers behind the Sinatra project for providing the inspiration for this framework. Thanks also to the team behind Boo, particularly Cedric Vivier, who provided a quick fix to a blocking ASP.NET code generation issue early-on. Without this great language, the opportunity to work on such a project wouldn't exist in the same form.
It's become apparent that, in the course developing web applications in ASP.NET that happen to have both a rich domain (where all of the business logic besides mapping from the client to the domain and vice versa is pushed into application services and not present in the server-side web layer) and a rich client (where the UI doesn't take orders from the server as much as merely poll it for data needed to drive it's own client-side logic and push data to it when needed), there is pretty narrowly focused set of tasks that web frameworks are best suited to and should be optimized for.
The tasks mentioned above are:
- Expose endpoints (Routes) and handlers (Actions) for them which the client can hit to GET a view, POST information, etc. Additionally specify Behaviors that target all or a specific subset of these routes to accomplish administrative tasks (user authentication, logging, etc) that would only add repetition and clutter to the route actions themselves.
- Deliver application dependencies to the handler for a route to aid the handler in carrying out its mission.
- Map data from the client to the domain and vice versa.
With these points in mind, it's easy to envision a world where the actual server-side web component of an application stack is somewhat anemic. Adding new routes and sections to a web application should be as easy as declaring a route and what ought to happen when a request that matches this route is received from the client. Yet, this doesn't seem to be the case in practice.
Mercury is a web framework that attempts to address this and other concerns. The third item in the above list is highly domain-dependent, so Mercury is mostly concerned with optimizing the first two. Additionally, the second item seems to be a greater concern in statically typed programming languages, a group which Boo counts itself among.
In order to understand how Mercury can make the web application aspect of your software developer life easier, let's start with some code:
Get "/":
return "Hello, world!"
This demonstrates the basic unit of externally exposed functionality in a Mercury web application: The route action. It has two major components: The Route portion, which consists of an HTTP Method (GET
, PUT
, POST
and DELETE
are supported) and a route string (the url-like portion that is between double-quotes). The other part of this route action is the Action body, or just action. It is the entire indented block that comes after Get "/"
. This is the code that is evaluated when it's enclosing route is matched.
The code block shown is a route that matches to "/"
or ""
(the default page for the site.. for example it'd match a call to http://www.example.com/
from the browser). The content of the route action's body is to write Hello, world!
directly to the output of the response (returning a string from an action body in mercury will cause that string to be written to the output). Let's show how much code this would take in an equivalent ASP.NET MVC app composed in everyone's favorite language, C#:
In your Application_Start()
, you'd need...
routes.MapRoute(
"Default", // Route name
"{controller}/{action}/{id}", // URL with parameters
new { controller = "Home", action = "Index", id = "" } // Parameter defaults
);
...in order to register the default routing scheme (controller/action convention and call HomeController.Index()
by default).
Then, in your HomeController
class, you'd need:
public ActionResult Index()
{
ControllerContext.HttpContext.Response.Output.Write("Hello, world!");
return null;
}
.. or something along those lines. And this is just a trivial example.
Even with the fact that adding the default route is a one time cost, this is still noisier than the Mercury Example. There's also the higher-level design concern of "tearing apart" a route from its action. When used off of the shelf, ASP.NET MVC wants all of the routes set up in a centralized fashion, as opposed to being located with the controllers/actions themselves. And while the controller/action convention that is prevalent in ASP.NET "MVC" solutions (FubuMVC, ASP.NET MVC and MonoRail, particularly) is more than satisfactory for most "entry-level" MVC apps, you'll soon find yourself adding routes as soon you start wanting prettier "REST-ful" style routes, like:
// In ASP.NET MVC, this action won't match on `http://example.com/User/foo` unless
// you added a route during `Application_Start()`. Within a specific route, this
// would only match on `http://example.com/User/Index?username=foo`.
public ActionResult Index(string username)
{
...
}
Mercury is philosophically opposed to the "implicit routing" prominent in other modern ASP.NET frameworks. Instead, it puts the onus on developers to declare the route for each action explicitly from the get-go. This is a feature and not a bug.
The Mercury codebase provides a Mercury.ExampleSite ASP.NET project which is a good place to start. It is broken down as follows:
Global.asax
andGlobal.asax.boo
-- The.boo
codebehind houses theApplication_Start()
that gets ran when an ASP.NET app starts up. Here is where you do your setup for your container of choice (any container that has an adapter forIServiceLocator
is supported, as discussed in the "Inversion of Control Containers in Mercury" section of this document. This is also where you add/configure any view engines you'd like to use. Mercury currently has wrappers for the Spark and NHaml view engines. The example site shows how to add support for both of them. This is also where theMercuryStartupService
is instantiated and it'sBuildRoutes()
method is called. The return value of that method should then be added to the static RouteTable as shown in ExampleSite.- The
Scripts
andContent
folders -- These are default folders from ASP.NET MVC used to house javascript files and CSS/images, respectively. Their use is already documented. - The 'Views' folder -- like Scripts/Context, this is also a convention-based folder from ASP.NET MVC. This is the default "root" folder where all view files should live. Whenever using Mercury's view engine rendering macros (
spark
ornhaml
, currently), it is assumed that the files are within this folder (so if you have a file inAppRoot\Views\NHaml\Index.haml
, you will use it at render time asnhaml "NHaml/Index.haml"
-- also note the use of forward-slashes when specifying the URI for the view file. - The
Routes
folder -- this folder houses all of the Mercury route actions. Mercury is unique amongst ASP.NET frameworks in that it tightly couples the routes and their implementation action bodies instead of splitting them up, presentationally. The route actions are located in the same project as the web site itself; this is by convention only and nothing is stopping you from moving these routes to another project, as long as the assembly containing the routes is referenced by the ASP.NET site. All referenced assemblies as searched at startup for Mercury route actions. Additionally, the Behaviors that target the various routes are housed alongside the routes, themselves. This is another convention-based choice and nothing says you can't put them in other files or projects, as they are parsed/loaded at startup in the same manner as routes. - The
Domain
folder -- another convention-based choice. In a real web site you will most likely house the domain in an external project. This is provided for organizational convenience, only. - The
web.config
for this project has an added dependency on theBoo.Lang.CodeDom
code generator.
One caveat to the the above Hello, world!
example: Because get
is a reserved keyword in Boo, you must use the Uppercase-G with GET routes. This is not required for the other verbs (Post
, Put
, Delete
). Also, GET
is an acceptable substitute, if you that's your thing.
Currently, Mercury uses the System.Web.Routing engine to parse and match on its routes. With this in mind, routes are defined in conformance to System.Web.Routing's standards with the exception of defining the default route for the root of a site. Typically, beginning a route with a /
character is not allowed, but Mercury allows it for the default route only, because it much more intention-revealing than having a route that matches on an empty string.
a If you'd like to turn an arbitrary .NET object into JSON, it's as simple as:
Get "summary/{username}":
dependency summaryService as IUserSummaryService
userSummary = summaryService.GetSummaryFor(username)
json userSummary
Of course, as noted in above examples, simply returning a string will cause that string to be written to the output:
Get "hello/world":
return "it's as simple as that!"
If you'd like to redirect to another route or URL, try:
Get "should/we/redirect/{answer}":
if answer.Equals('yes'):
redirect "/target/url"
else:
return "nope.. no redirects here"
Should it, in the course of processing a route's body, become desirable to halt execution and return an http error, you can do:
Get "something/goes/wrong":
rand = Random()
if rand.Next(0,100) > 50:
halt 500
else:
return "less than 50"
You can also add an optional message to be returned in the output and error status description:
Get "something/goes/wrong":
rand = Random()
if rand.Next(0,100) > 50:
halt 500, "Looks like it was greater than 50, to me"
else:
return "less than 50"
You can also return sub-status codes:
Get "something/goes/wrong":
rand = Random()
if rand.Next(0,100) > 50:
halt 500.13, "web server too busy"
else:
return "less than 50"
Of course, if you'd like to return nothing at all from your method's route body, that's fine as well. Boo's compiler will just slip in a return null
statement for you at the end to keep things kosher. null
results are ignored by the framework.
If you'd like to have a route action that is processed when a request's url doesn't match on any existing route, you can define a not_found
route action like so:
not_found:
return "route not found, sorry."
And this route action will be processed in the event that the URL of a request doesn't match an existing route or resource. Please note that, because of the default scheme for mapping resources for the Scripts
, Content
and Views
folders in an ASP.NET project, malformed requests in these URI-spaces won't be handled by a not_found
route action.
Out-of-the-box, Mercury will set up several default local variables for various useful things in that you'll want to have handy in the route's body.
For every parameter you declare for a route (using {param_name}
syntax), Mercury will declare a matching local variable with the same name as your parameter and populate it with the value in the request as a string. You can also, optionally, use the params
object to access route parameters using the typical string indexor or a IQuackFu-based approach, like so:
Get "route/{parameter_name}":
if parameter_name in (parameter_name, params["parameter_name"], params.parameter_name):
return "they all point to the same string. neat, huh?"
In the above example, the params
object is read only. Do not attempt to store variables in it.
The views
and temp
variables are, on the other hand, read/write:
Get "route/{parameter_name}":
view.parameter_name = parameter_name
temp.parameter_name = view["parameter_name"]
view
and temp
are wrappers around the ViewData
and TempData
properties of ControllerBase from ASP.NET MVC, respectively.
If you need more low-level access to a request, response, etc.. you can always use the self.ControllerContext
property to get that underlying access. It behaves exactly as it does in ASP.NET MVC.
Here's a non-exhaustive list of reserved identifiers/keywords in Mercury:
- Macros for declaring routes, behaviors, etc:
Get
,Post
,Put
,Delete
,Any
andnot_found
.Behavior
for declaring behaviors.dependency
to declare a new dependency in a macro body or behavior. params
,view
andtemp
in the route body.- For every parameter delcared withing curly braces, like
{parameterName}
, a local variable (as astring
) is declared in the Route body's scope.
Any modern, non-trivial ASP.NET application that isn't a WebForms ball-of-mud is going to want to provide a Dependency Injection solution for it's exposed actions, sooner or later.
In ASP.NET land, the typical pattern to accomplish this is to declare dependencies in the constructor for a controller class and have some method at the web framework level to inject those dependencies into an instance of the controller at the time of a client request. This approach can lead to the instinctual tendency to aggregate actions with similar dependency requirements that don't necessarily belong together from a site layout/behavioral standpoint. With a sufficiently complex feature, a controller's size in code and scope can quickly get out of control.
Mercury handles for this case as a core feature. Consider the following:
Post "Widgets/MoveWidgets/{fromShelf}/{toShelf}/{amount}":
dependency widgetServices as IWidgetServices
widgetServices.MoveWidgets(fromShelf, toShelf, amount)
redirect "Widgets/Summary"
Get "Widgets/Summary":
dependency widgetReporter as IWidgetReporter
view.WidgetsSummary = widgetsReporter.WhereAreTheWidgets()
spark "Widgets/Summary"
Now, there's a lot of interesting things going on in this example. But let's concentrate on the first line of the first route action's body:
dependency widgetServices as IWidgetServices
It's pretty obvious what's going on here, right? The route action states, up front, that it has a dependency on a IWidgetServices and it'd like the instance of that service to be called widgetServices. What's interesting here that the two routes don't share any dependencies, in this case. If this were an ASP.NET MVC project, it'd look something like:
public class AccountController : Controller
{
IWidgetServices _widgetServices;
IWidgetReporter _widgetReporter;
public WidgetsController(IWidgetServices widgetServices, IWidgetReporter widgetReporter)
{
_widgetServices = accountServices;
_widgetReporter = accountReporter;
}
public ActionResult MoveWidgets(int fromShelf, int toShelf, decimal amount)
{
_widgetServices.MoveWidgets(fromShelf, toShelf, amount);
return Redirect("Widgets/Summary");
}
public ActionResult Summary()
{
ViewData["WidgetsSummary"] = _widgetReporter.WhereAreTheWidgets();
return View();
}
}
There are, primarily, two things worth noting about this C# example: First, compared to the Mercury example, it's quite noisy with all the setup code that goes into establishing dependencies and whatnot. Second, the two actions (MoveWidgets
and Summary
) share all of their dependencies, even though each one doesn't use the other's. A third and somewhat tangential issue is that MoveWidgets
doesn't have a more REST-ful looking route out-of-the-box and is going to require a route to be registered during Application_Start()
. This is the last time that this shortcoming will be mentioned, honest :)
For the record, Mercury implements its DI scheme in a manner very similar to how other ASP.NET frameworks declare dependencies: the constructor. At compile-time (expansion-time, technically), the code for each route action turns into an AST and is transformed into an AST for a class definition. It is this class that has the dependencies declared as parameters to its constructor. From here, the DI is done in a manner pretty much the same as other frameworks. Overall, the end results are the same. It's just that Mercury is much less noisy about it.
But all of this doesn't even take into the account the need to setup a mechanism which, presumably, is going to do the actual heavy-lifting of delivering the required dependencies to your controller at the time of a client request. In Mercury, this is accomplished through the use of any number of different Inversion of Control containers. Mercury only requires that the container implementation you use has an adapter that satisfies the IServiceLocator interface. Mercury's ExampleSite uses Machine.Container, but this doesn't stop you from using any other container if you so desire.
In order to demonstrate how Mercury integrates with an IoC container, let's just show the code.
In a Mercury project's Global.asax:
public class MercuryApplication(System.Web.HttpApplication):
protected static def ConfigureContainer() as IServiceLocator:
container = SomeContainerImpl()
// container configuration, registration of deps, etc goes here.
return container
// or whatever it takes to get an IServiceLocator for your container of choice
public static def ConfigureMercuryEngine() as Route*: // Factoid: T* is Boo shorthand for IEnumerable[of T]
container = ConfigureContainer()
engine = MercuryStartupService(container, ViewEngines.Engines)
routes = engine.BuildRoutes()
for route in routes:
RouteTable.Routes.Add(route.Url, route)
return routes
protected def Application_Start():
ConfigureMercuryEngine()
In this example, you can see how the MercuryStartService
takes an IServiceLocator
in its constructor. It will use the container during client requests to satisfy the dependencies for routes. With this in mind, you (the developer) just have to make sure to register the dependencies and Mercury will take of the rest. That's all there is to it, really.
In ASP.NET MVC, not so much. The main annoyance is that you'll have to provide an implementation of IControllerFactory
that does the actual work of resolving controller instances with dependencies at runtime. This isn't such a big deal, but it's still an inconvenience. Other frameworks are kind enough to have the concept of a container baked in, but make the design mistake (in the author's opinion) of tightly coupling the framework to a single container implementation (you know who you are).
Like most other ASP.NET frameworks, Mercury has a concept of Behaviors (known as filters in some other frameworks).
A demonstration, if you will:
behavior AuthenticatedUsersOnly:
target ".*"
target_not "Public"
target_not "User/Login"
dependency userAuthService as IUserAuthenticationService
before_action:
redirect "User/Login" if not userAuthService.sessionIsAuthentication(controllerContext.HttpContext.Session)
Get "User/Login":
// login stuff goes here
pass
Get "User/Summary":
// user summary goes here
pass
A few things worth mentioning in this example:
-
The behavior has a name. Any name for a valid identifier (it actually expands into a class) is allowed. This is so that behaviors can associate themselves with each other (an idea which will be expanded up very shortly).
-
Second, the behavior targets a specific subset of routes, as determined by the provided regular expression string that accompanies the
target
statement. A behavior must provide at least onetarget
statement, but can provide as many as it wants if need be. If you want your behavior to target all routes, just have it target '.*
'. This is important to note, because behaviors are parsed globally, regardless of where they're located. Bear this in mind when defining thetarget
(s) for a route.target_not
is a simple inversion of thetarget
concept: a route that matchestarget_not
will be excluded from the subset of targeted routes. Please be aware thattarget_not
takes precedence overtarget
. This is to help you, the developer, keep your targets relatively simple and intention-revealing (as opposed to being laden with write-only regular expressions). -
Third, the behavior has a
before_action
block that is pretty self-explanatory. Anafter_action
is also supported. Also, as you can see, thebefore_action
has a call to a local variable calledcontrollerContext
. This is an instance of the ASP.NET MVC ControllerContext, which provides access to the request and response, amongst other things. Theafter_action
blocks also gets a shot at the object returned as the result of the route, in addition to theControllerContext
. -
Fourth, behaviors can take dependencies just like route actions.
-
Finally, the behavior does a redirect if the client making the request is not authenticated. From this we can see that behaviors can effect the course of a request in much the same ways a route action can.
To expand upon the utility of the first item mentioned above:
behavior AuthenticatedUsersOnly:
target_not "Public"
target_not "User/Login"
dependency userAuthService as IUserAuthenticationService
before_action:
redirect "User/Login" if not userAuthService.sessionIsAuthentication(controllerContext.HttpContext.Session)
view.username = controllerContext.HttpContext.Session["username"]
behavior FetchUserInfo:
target ".*"
target_not "Public"
target_not "User/Login"
run_after AuthenticatedUsersOnly
dependency userInfo as IUserInfoService
before_action:
view.UserInfo = userInfo.GetFor(view.username)
behavior LogStuff:
dependency log as ILogger
run_last
target ".*"
after_action:
pass // logging stuff goes here
What's worth pointing out, here? In the second behavior, FetchUserInfo
, there is a run_after
statement that says quite plainly that the behavior should run after AuthenticatedUsersOnly
.
The behavior is declaring its precedence in relation to another behavior. run_before
is also supported. With this, you can construct dependency sequences of behaviors for a given set of routes. It should be noted, however, that an exception will be thrown at startup if a given behavior has a precedence relationship with another behavior that does not target the same set of routes. Cyclic precedence relationships will also cause an exception. Please bear this in mind when setting up your behaviors.
Also, interestingly, the LogStuff
behavior has a run_last
statement. This does what you might expect. There is also a run_first
statement. An exception will be thrown on startup if a given route has more than one run_first
or run_last
behavior targeting it.
Currently, Mercury supports rendering views defined in NHaml and Spark. Brail support is planned.
Render semantics work like:
Get "nhaml\helloworld":
nhaml "nhaml\foo.haml"
Get "spark\helloworld":
spark "spark\foo.spark"
The mentioned early in the document, the provided paths in these example route actions would map to AppRoot\Views\nhaml\foo.haml
and AppRoot\Views\spark\foo.spark
, respectively. Please be mindful that, if you're going to use Mercury's view engine wrappers, you'll want to add the view engines on startup:
protected static def ConfigureViewEngines() as ViewEngineCollection:
ViewEngines.Engines.Add(MercurySparkViewEngine())
ViewEngines.Engines.Add(MercuryNHamlViewEngine())
return ViewEngines.Engines
... This is the fashion in which it is done in Mercury.ExampleSite. Note that Mercury provides wrapper classes for NHaml and Spark's own view engines implementations.