Table of contents
- 1 Stating the problem
- 2 How MonoRail tackles this problem
- 3 How it works
- 4 The initialization flow
- 5 Lifecycle interfaces
- 5.1 Invocation order
- 5.2 The expected (good) behavior from a service implementation
- 6 Built-in services
- 6.1 MonoRailConfiguration
- 6.2 ExtensionManager
- 6.3 IViewSourceLoader
- 6.4 IViewEngine
- 6.5 IScaffoldingSupport
- 6.6 IControllerFactory
- 6.7 IViewComponentFactory
- 6.8 IFilterFactory
- 6.9 IResourceFactory
- 6.10 IEmailSender
- 6.11 IEmailTemplateService
- 6.12 IControllerDescriptorProvider
- 6.13 IResourceDescriptorProvider
- 6.14 IRescueDescriptorProvider
- 6.15 ILayoutDescriptorProvider
- 6.16 IHelperDescriptorProvider
- 6.17 IFilterDescriptorProvider
- 6.18 IControllerTree
- 6.19 ICacheProvider
- 7 Registering services
MonoRail Services Architecture
MonoRail uses a set of services to handle specific tasks. The framework is responsible for defining the default implementations, instantiate, start and configure them. The services are made available through a combination of lifecycle interfaces and an implementation of IServiceProvider
Stating the problem
It's impossible to come up with a sophisticated software where the default behavior pleases everyone and integrates with everything. The usual solution is making the software rely on contracts and having the core code as just a coordination of invocations on the contracts implementation. An user is thus capable of replacing one or more contract implementation.
The challenging is implementing an architecture where the parts are easily replaced, configurable and can rely (depend) on other parts.
How MonoRail tackles this problem
The most usual solution to this problem is to use an Inversion of Control container. However, things have to be balanced. For MonoRail, an IoC container would introduce dependencies on assemblies and an longer initialization. In the end, we wouldn't benefit from all IoC container features, so it could be considered too much for our problem.
Instead, we combined what is already on the .Net library and some creative solution.
Basically we create two levels of services registries, per application and per request, and a simple interfaces that defines lifecycles that services optionally implement. This allow the service to start its work when it is supposed to and to gather reference to other services.
How it works
When the web application is started, the ASP.Net modules are initialized. MonoRail has a EngineContextModule which is in charge of
- Read the configuration
- Initialize the services
- Subscribe to ASP.Net application and request level events
- Create a request context (which we'll not cover here)
Services implementation can be defined in the configuration section. After reading the configuration section MonoRail checks for missing definition and register the missing services using the default implementation.
After that it instantiates every service and runs the lifecycle. If everything went well, the framework is properly initialized. All services are registered in the application level container, which happens to be implemented by the EngineContextModule class. We call this the parent container.
When a request starts, MonoRail creates a DefaultRailsEngineContext instance, which also is a container for services. MonoRail sets the parent container on it. This allow the user to override services per request, and resolution of services in the parent.
The initialization flow

- The configuration is read into the MonoRailConfiguration. It's also registered as a service
- The services collected are instantiated and registered
- The lifecycle is executed
Lifecycle interfaces
A service might implement a few interfaces to expose to MonoRail that it behaves in a specific way or that it needs something from the framework.
ISupportInitialize (from System.ComponentModel)
This interface can be implemented if a service wants performs some initialization.
IInitializable (from Castle.Core)
This interface can be implemented if a service wants performs some initialization.
IServiceEnabledComponent (from Castle.Core)
This interface can be implemented if a service uses other services.
Invocation order
IServiceEnabledComponent is the first one to be invoked. This gives a chance to services gather all services references it wants. Then the initialization interface's methods are invoked.
The expected (good) behavior from a service implementation
For service that uses IServiceEnabledComponent lifecycle, the implementor should keep in mind that the initialization lifecycle has not run for all services, so it might not be safe to use other services as they might not be properly initialized at the moment.
The order of service registration and instantiation is not guaranted. So the implementor should not make any assumption regarding it.
Built-in services
The following is a succint list of services and their roles. You can refer to this list to learn more about MonoRail inner workings or to use them when developing extensions and new services.
MonoRailConfiguration
Exposes the MonoRail configuration
ExtensionManager
Manages registered extensions dispatching events from Asp.Net infrastructure and from MonoRail services
IViewSourceLoader
Sits in front of the file system and from assembly resources. It is used by view engines to obtain view streams
IViewEngine
Process view templates
IScaffoldingSupport
Adds scaffold support to a controller
IControllerFactory
Creates the controller instances
IViewComponentFactory
Manages registered ViewComponents and creates their instances
IFilterFactory
Manages registered filters and creates their instances
IResourceFactory
Create resources
IEmailSender
Sends e-mail
IEmailTemplateService
Process e-mail templates using the MonoRail infrastructure
IControllerDescriptorProvider
Inspects Controller types building a descriptor of what has been defined using attributes
IResourceDescriptorProvider
Creates descriptors for resources declared on controllers
IRescueDescriptorProvider
Creates descriptors for rescues declared on controllers
ILayoutDescriptorProvider
Creates descriptors for layouts declared on controllers
IHelperDescriptorProvider
Creates descriptors for helpers declared on controllers
IFilterDescriptorProvider
Creates descriptors for filters declared on controllers
IControllerTree
Manages a binary tree of controllers registered
ICacheProvider
Manages the cache
Registering services
You have to use the monorail configuration node to override or add services to MonoRail.