Introduction
This section covers a series of advanced topics related to MonoRail.
Security
There are a few security related issues you should consider when configuring your MonoRail application.
First, if your view directory is in the web folder then clients can potentially see the source code of the views, which can expose potentially sensitive information to parties you would prefer not to have access to it. To prevent this, associate the view extension with an
IHttpHandler that comes with ASP.Net.
Second, if you use the
DataBinder to populate classes, you might want to provide an
Exclude or
Allow list to prevent people from populating properties that are not on the form. Check the
DataBind documentation for more information.
Routing
MonoRail supports mapping arbitrary URLs to specified area/controller/action combinations. Parameters can be matched based on regular expressions, text or numerical values and a fluent API is provided for creating routing rules. Further information can be found in the
Routing topic.
Caching Support
TBD.
Transformation Filters
Transformation Filters allow you to manipulate the stream of data that defines a generated page before it is sent to the client.
Right before a generated page is streamed to the client, you can use a Transformation Filter to manipulate the stream. The Transformation Filter is applied after the view engine has built the page. Your view engine is not used with a Transformation Filter.
To assign a Transformation Filter to an action, use the
TransformationFilter attribute.
public class UserController : Controller
{
[TransformationFilter(typeof(UpperCaseTransformationFilter))]
public void View()
{
}
}
In the example above, the entire page generated by the View action will be converted to upper case text.
You may apply multiple Transformation Filters to an action. To control their order of execution, use the
ExecutionOrder property.
public class UserController : Controller
{
[TransformFilter(typeof(WikiTransformFilter), ExecutionOrder=1), TransformFilter(typeof(UpperCaseTransformFilter), ExecutionOrder=2)]
public void View()
{
}
}
In the example above, the page generated by the View action will first be transformed using the
WikiTransformFilter and then the result of that will be transformed to upper case text.
You can create your own Transformation Filters by inheriting from the
TransformFilter abstract base class.
Dynamic Actions
Dynamic actions and action providers are a way to create custom and dynamic functionality.
MonoRail considers every public instance method of a controller an action, for certain situations this is not enough. For example, you want to decide on the name for a section in the URL at runtime when the data is available; or a set of controllers that inherit from a distinct common super class need the same action which can be defined in the super class.
Dynamic Actions are a way to associate code with a name in runtime.
A dynamic action is nothing more than an implementation of the interface
IDynamicAction:
public interface IDynamicAction
{
/// <summary>
/// Implementors should perform the action
/// upon this invocation
/// </summary>
void Execute(Controller controller);
}
You can associate a dynamic action with a controller using the
DynamicActions property:
public class MyController : Controller
{
public MyController
{
DynamicActions["index"] = new IndexDynamicAction();
}
}
Dynamic Action Providers
Dynamic Action Providers are in charge of adding dynamic actions to controllers. They can be associated with controllers using the
DynamicActionAttribute:
[DynamicAction(typeof(MyDynActionProvider))]
public class MyController : Controller
{
public MyController
{
}
}
The implementation of the provider can be something static (ie. always add the same set of actions) or can "read" something from the controller or the logged user; and add the actions accordingly:
public class MyDynActionProvider : IDynamicActionProvider
{
public void IncludeActions(Controller controller)
{
controller.DynamicActions["index"] = new IndexDynamicAction();
}
}
Scaffolding
Scaffolding is a idea borrowed from
Ruby on Rails. It refers to the ability to create pages with a simple interface to data in a database with very little effort.
As every project under the Castle Project umbrella does not obligate you to embrace it all, scaffolding is implemented with the
IScaffoldingSupport interface.
MonoRail will instantiate the implementation specificed if it discovers a controller with one or more
ScaffoldingAttribute:
[Scaffolding(typeof(Blog))]
public class BlogsController : Controller
{
public BlogsController()
{
}
}
The scaffolding implementor should register actions as a dynamic action provider.
The default implementation of scaffolding support relies on Castle ActiveRecord and is discussed in the
ActiveRecord section.
Extensions
From MonoRail beta 5 you can use the new Extensions support which allow you to plug existing extensions or develop your own extensions. This document should clarify why extensions were introduced, a list of out-of-box extensions and how to create your own extensions.
The user and development community on MonoRail is quite active, and consequently we receive lots of suggestion about new interesting features. Some of the features are unarguably helpful for the almost every user, however most of them are only useful to some specific users/scenarios. So implementing the new feature directly into the core framework would not make much sense as they usually increase the request flow overhead and the users that are not using the feature would be penalized.
Extensions were introduced to allow the framework to be extended easily and extensions to be reused.
Creating Custom Extensions
Creating an extension is a fairly simple task. Just create a new public class and implement the
IMonoRailExtension or extend from
AbstractMonoRailExtension. The latter is simpler.
After that you can override the methods you want. Usually your extension will read some custom configuration from the MonoRailConfiguration to configure itself if necessary and then perform some action when one of the hook methods is invoked.
MonoRail must be told that an extension exists. You can do that by using the
extensions node in the configuration file. For more on that, check the MonoRail Configuration Reference document.
Built In Extensions
With the beta 5 version we shipped two extensions. As they can be helpful for some scenarios, they were also developed to serve as sample extensions, so the code is very simple.
Custom Session Extension
The Custom Session extension allows you to plug a custom implementation for the session available on
IRailsEngineContext . This can be useful if you have requirements to implement on a session that ASP.Net Session strategies can not fulfil.
In order to use this extension you must provide an implementation of
ICustomSessionFactory which would be responsible to create your own session implementation. A really naive implementation could map some cookie value to an instance of
Hashtable, for instance.
Extensions were introduced to allow the framework to be extended easily and extensions to be reused.
You need to install the extension using the extensions node, as usual, and also provide the attribute
customsession to inform the type that implements
ICustomSessionFactory as follows:
<monorail customsession="Type name that implements ICustomSessionFactory">
<extensions>
<extension
type="Castle.MonoRail.Framework.Extensions.Session.CustomSessionExtension, Castle.MonoRail.Framework" />
</extensions>
</monorail>
Exception Chaining Extension
The Exception Chaining extension allows you to execute one or more steps in response to an exception thrown by an action. The steps are called Exception Handlers and must implement
IExceptionHandler (or
IConfigurableHandler if they need external configuration).
The
IExceptionHandler interface is very straightforward; it simply dictates the contract for processing the exception information. As they are chained you must be good and check if there's a next handler available and if so, delegate the invocation to it. It would be also a good behavior if your handler implementation doesn't throw exceptions at all.
It is also important to note that you can use the
AbstractExceptionHandler to save you some few types.
The
IConfigurableHandler is just an increment the previous interface for those handlers that require configuration information. The
Configure method is invoked as soon as the handler is instantiated and its node on the configuration is passed.
The extension does not do much more than delegating the execution to the installed handlers. You can create handlers to provide actions and even introduce new semantics. As the handlers are chained together, you can even implement a handler that decides if the execution chain should continue or stop right there. For example, suppose you want that only exceptions that extends
SqlException be e-mailed to you. In this case you could write this simple handler:
public class MyFilterHandler : AbstractExceptionHandler
{
public override void Process(IRailsEngineContext context, IServiceProvider serviceProvider)
{
if (context.LastException is SqlException)
{
InvokeNext(context, serviceProvider);
}
}
}
And of course, register this handler before others.
The EmailHandler¶
MonoRail comes with just one exception handler:
EmailHandler . This handler e-mails the exception details and the environment details to a specified e-mail address.
This handler requires the attribute
mailto and you can optionaly inform the
mailfrom as well. Also, you must provide the
smtpHost in the configuration -- see MonoRail Configuration Reference for more details on this one.
<monorail smtpHost="my.smtp.server">
<extensions>
<extension
type="Castle.MonoRail.Framework.Extensions.ExceptionChaining.ExceptionChainingExtension, Castle.MonoRail.Framework" />
</extensions>
<exception>
<exceptionHandler
mailTo="lazydeveloper@mycompany.com" mailFrom="angry.client@client.com"
type="Castle.MonoRail.Framework.Extensions.ExceptionChaining.EmailHandler, Castle.MonoRail.Framework" />
</exception>
</monorail>
You need to install the extension using the
extensions node, as usual, and also provide the node exception to list the handlers you want to install. Please note that they will be installed and chained in the same order they were declared.
<monorail customsession="Type name that implements ICustomSessionFactory">
<extensions>
<extension
type="Castle.MonoRail.Framework.Extensions.ExceptionChaining.ExceptionChainingExtension, Castle.MonoRail.Framework" />
</extensions>
<exception>
<exceptionHandler type="type that implements IExceptionHandler" />
</exception>
</monorail>
Service Architecture
It's impossible to come up with 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.
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.
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 a longer initialization time. 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 solutions.
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 allows 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 an
EngineContextModule which is in charge of
- Reading the configuration
- Initializing the services
- Subscribing 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 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.
IServiceEnabledComponent is the first one to be invoked. This gives a chance to gather all services references it wants. Then the initialization interface's methods are invoked.
For services that use the
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.
Registering Services
You have to use the monorail configuration node to override or add services to MonoRail.
Built-In Services
The following is a list of build-in 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
Custom Bindable Parameters
When an action is invoked on a controller that extends
SmartDispatcherController, it inspects the parameters for attributes that implement
IParameterBinder. The
DataBindAttribute, for example, is one of these attributes.
This allows you to create binding logic, or add validation or anything that you want. You just need to create an attribute that applies to method arguments and implements the
IParameterBinder.
Using Resources to Store Views
Aside from the file system you can have views on the resource assemblies. This feature was introduced due to a necessity to reuse controllers, filters and views among projects, for example an user administration module.
To configure the assemblies that have views, use the
additionalSources configuration node. See MonoRail Configuration Reference document.
Enabling Logging
You can supply an implementation of
ILoggingFactory so MonoRail can use logger for its internal pieces and allow you to use the
Controller.Logger.
Use the
services node with the key
Custom. The example below uses
log4net. Make sure you have a
log4net.config on the root directory that configures log4net.
<monorail>
<services>
<service
id="Custom"
interface="Castle.Core.Logging.ILoggerFactory, Castle.Core"
type="Castle.Services.Logging.Log4netIntegration.Log4netFactory, Castle.Services.Logging.Log4netIntegration" />
</services>
...