Welcome Guest, you are in: Login

Castle Project

RSS RSS

Navigation (MonoRail)





Search the wiki
»

PoweredBy

Advanced Topics

RSS
Modified on 2011/01/10 17:58 by Jan Wilson Categorized as Uncategorized

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.

Image

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>
	
	...

ScrewTurn Wiki version 3.0.4.560. Some of the icons created by FamFamFam.