Archive

Archive for the ‘MonoRail’ Category

MonoRail custom rescue controller (IRescueController)

9 January, 2009 1 comment
The problem

I am calling a web service from a controller and the web service throws a WebException with different exception messages, one when the remote server is unavailable and another when the remote server times out. I’d like to show a rescue view based on the exception type and message not just it’s type. 

The standard Monorail Rescue attribute only takes an exception type so there didn’t seem to be an easy way to solve my problem. I could have a generic rescue view which shows an error for all WebExceptions but I’d like to be a bit more specific. I could send all WebExceptions to the same rescue view and then handle the different messages in the view but I didn’t want the logic in my rescue view.

I noticed in the trunk (revision 5407) the Rescue attribute takes a rescue controller which I’d never seen before. I started digging around the web and source and came up with the following solution which works although I not sure its the best solution!

My solution

I changed my base controller to use a rescue controller instead of rescue views:

[Rescue(typeof(RescueController))]
public class ApplicationController : SmartDispatcherController

I then created a RescueController:

[Layout("default")]
public class RescueController : SmartDispatcherController, IRescueController
{
	public void Rescue(Exception exception, IController controller, IControllerContext controllerContext)
	{
		SetRescueView("generalerror");
		if (exception.GetType() == typeof(WebException) && exception.Message == "Unable to connect to the remote server") SetRescueView("webexception_cannot_connect");
		if (exception.GetType() == typeof(WebException) && exception.Message == "The operation has timed out") SetRescueView("webexception_portal_timeout");

	}

	private void SetRescueView(string viewname)
	{
		RenderSharedView(Path.Combine("rescues", viewname));
	}
}

The Rescue method simply checks the exception type and message and if it gets a match it sets the relevant rescue view. Notice I’m using a little helper method to set the rescue view this tells Monorail look in the standard folder Views\rescues for the view otherwise it would be looking for these views in Views\Rescue which could get confusing.

Gotchas!
  • Make sure your custom rescue controller inherits from SmartDispatcherController otherwise your custom controller will not get called. See Castle Project Users list for more detail
  • Add the Layout attribute to the Rescue controller if you want  to use your standard layout on the rescue pages.
  • If your using the Windsor container remember to add the Rescue controller to your container otherwise your custom rescue controller will not be found and called. This one had me scratching my head for a while!
  • Make sure your rescue views exist in the Views\rescues folder otherwise you will get the standard ASP.NET error page and it will look like none of this has worked.
  • This works with the trunk (revision 5407) not sure when the IRescueController functionality was added so it may not work with earlier revisions.
A better solution?

I thought a better solution would be to add a sub string message to the Rescue attribute which could be used to match the exception type and message. E.g:

Rescue("web_exception_unable_to_connect", typeof(WebException), "Unable to connect to remote server")
Rescue("web_exception_timeout", typeof(WebException), "The operation has timed out")

I couldn’t see an easy way to extend the standard Monorail rescue functionality so I ended up creating a builder which allowed me to define a map of exceptions to rescue views.

builder.Map<WebException>()
	.WithMessageContaining("Unable to connect to the remote server")
	.OrMessageContaining("The remote server returned an error: (403) Forbidden.")
	.ToRescue("webexception_cannot_connect");

builder.Map<WebException>()
	.WithMessageContaining("The operation has timed out")
	.ToRescue("webexception_portal_timeout");

My final rescue controller simply uses the map to select the correct rescue view, no more if statements needed.

[Layout("default")]
public class RescueController : SmartDispatcherController, IRescueController
{
	private readonly IExceptionToRescueMapper _mapper;

	public RescueController(IExceptionToRescueMapper mapper)
	{
		_mapper = mapper;
	}

	public void Rescue(Exception exception, IController controller, IControllerContext controllerContext)
	{
		SetRescueView(_mapper.GetRescueFor(exception));
	}

	private void SetRescueView(string viewname)
	{
		RenderSharedView(Path.Combine("rescues", viewname));
	}
}

If you know of a better way I’d love to hear it…

Categories: MonoRail Tags:

Monorail + NVelocity + How to find out if your in the first item of a foreach loop?

17 May, 2007 Comments off

A colleague needed to add a button to the first row of a table but how do you do this in a foreach loop?

NVelocity has a property called $velocityCount which you can check within the foreach loop:

<table>
#foreach($detail in $details)
  <tr>
    <td>
      $detail.ShortText)
      #if($velocityCount == 1) First Row! #end
    </td>
  </tr>
#end
</table>
Categories: MonoRail

Castle Monorail + Active Record + Testing + CreateSchemaFromFile

5 December, 2006 2 comments

It’s always a good idea to have your test database match your production database exactly to do that use:

ActiveRecordStarter.CreateSchemaFromFile()

Unlike CreateSchema which takes a best guess at how your schema should be CreateSchemaFromFile uses an SQL script which allows you to control exactly what the schema should be.

Call CreateSchemaFromFile as part of your PrepareSchema method in your AbstractTestCase class.

protected virtual void PrepareSchema()
{
// If you want to delete everything from the model.
// Remember to do it in a descendent dependency order
// Office.DeleteAll();
// User.DeleteAll();
// Another approach is to always recreate the schema
// (please use a separate test database if you want to do that)
ActiveRecordStarter.DropSchema();
ActiveRecordStarter.CreateSchemaFromFile("schema.sql");
}

Your schema.sql file should contain scripts in the standard MS SQL format:

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Application_Status]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Application_Status]
GO

CREATE TABLE [dbo].[Application_Status] (
[ID] [int] NOT NULL ,
[Title] [varchar] (50) NOT NULL ,
[Description] [text] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

Categories: MonoRail

FW: Castle Projects + Monorail + 1.0 Release Candidate 2

10 November, 2006 Comments off

I’m a little late with this one (I’ve been busy honest!) the Castle project have a new release:

1.0 Release Candidate 2 – November 1st 2006

They have also have a jazzy new site and updated documentation too. Top work!

Categories: MonoRail

Castle Monorail + Active Record + Testing + CreateSchemaFromFile

27 July, 2006 Comments off

It’s always a good idea to have your test database match your production database exactly, we can do that easily using:

ActiveRecordStarter.CreateSchemaFromFile()

Unlike CreateSchema which makes a best guess at how your schema should be CreateSchemaFromFile takes an SQL script which allows you to fully control the database schema.

Call CreateSchemaFromFile as part of your PrepareSchema method in your AbstractTestCase class.

protected virtual void PrepareSchema()
{
ActiveRecordStarter.DropSchema();
ActiveRecordStarter.CreateSchemaFromFile("schema.sql");
}

Your schema.sql file should contain scripts in the standard MS SQL format:

if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[Application_Status]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[Application_Status]
GO

CREATE TABLE [dbo].[Application_Status] (
[ID] [int] NOT NULL ,
[Title] [varchar] (50) NOT NULL ,
[Description] [text] NULL
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO

Categories: MonoRail

Castle Monorail – Moving from Beta5 to latest build

29 June, 2006 Comments off

I’ve decided to move my “toy” MonoRail project from the Beta 5 version to the latest build so I can have a play with the new features. Mostly Formhelper!

Moving to the latest build broke a few things in my solution:

The Build

Nunit framework

My web test project was using Nunit version 2.2.0.0. Which is used by Castle.MonoRail.TestSupport I believe although don’t quote me on that I could be wrong. Any who it now requires the latest version of the nunit.framework.dll (2.2.8.0). I change the reference and all was well.

DataBindAttribute
Next up was the DataBindAttribute which appears to have changs slightly. I did have:

public void LoginSubmit([DataBind(Prefix = "student")] Student student)

It appears the Prefix parameter is now explicit, a simple change:

public void LoginSubmit([DataBind("student")] Student student)

The Tests

Ok now my solution builds but all of my ActiveRecord unit tests fail. I get the following error:

TestFixture failed: You can't invoke ActiveRecordStarter.Initialize more than once

Turns out to be a problem in the InitFramework method of my AbstractModelTestCase class. Initalialze is being called twice:

protected virtual void InitFramework()
{
IConfigurationSource source = ConfigurationManager.GetSection("activerecord") as IConfigurationSource;

ActiveRecordStarter.Initialize(source);

ActiveRecordStarter.Initialize( source, typeof(Student), typeof(LoginAudit) );
}

I guess there is now no need for the first ActiveRecordStarter.Initialize(source); call so I remove it and all is well!

The Run

Ok now my solution builds and passes the test but all is not well at run time I get the following error:

Looks like you forgot to register the http module Castle.MonoRail.Framework.EngineContextModule
Add '<add name="monorail" type="Castle.MonoRail.Framework.EngineContextModule, Castle.MonoRail.Framework" />' to the <httpModules> section on your web.config

This is a breaking change mentioned in the release notes and you must update your web.config. But don’t get your httpModules and your httpHandlers mixed up as I did. Simply add the httpModules to your web.config:

<system.web>
<httpHandlers>
<add verb="*" path="*.aspx" type="Castle.MonoRail.Framework.MonoRailHttpHandlerFactory, Castle.MonoRail.Framework" />
</httpHandlers>
<httpModules>
<add name="monorail" type="Castle.MonoRail.Framework.EngineContextModule, Castle.MonoRail.Framework" />
</httpModules>
</system.web>

The End

That’s it we’re down! Now I get to play with the latest version.

Categories: MonoRail

Castle Monorail – NVelocity View Engine

22 June, 2006 8 comments

I’m using the recommended view engine NVelocity.

The following objects are available to the view code:
• context (IRailsEngineContext)
• request
• response
• session
• All entries in the request (if any)
• All entries in the Flash (if any)
• All entries in the PropertyBag (if any)

Also, a siteRoot string is added so you can build url definitions like:
$siteRoot/home/index.rails
<img src="$siteRoot/images/someimage.gif" />

Notation (variable name):

$ [ ! ][ { ][ a..z, A..Z ][ a..z, A..Z, 0..9, -, _ ][ } ]

Examples:

  • Normal notation: $mud-Slinger_9
  • Silent notation: $!mud-Slinger_9
  • Formal notation: ${mud-Slinger_9}

Loops
#foreach($i in $items)
#each (this is optional since its the default section)
text which appears for each item
#before
text which appears before each item
#after
text which appears after each item
#between
text which appears between each two items
#odd
text which appears for every other item, including the first
#even
text which appears for every other item, starting with the second
#nodata
Content rendered if $items evaluated to null or empty
#beforeall
text which appears before the loop, only if there are items
matching condition
#afterall
text which appears after the loop, only of there are items
matching condition
#end

Binary expressions
#if($order.Status == "Undefined")
Sorry, but we don't know this order.
#elseif($order.Status == "Created")
Your order is being processed. Hold on!
#elseif($order.Status == "Dispatched")
Your order has been dispatched through UPS. Cross your fingers!
#end

IDictonary Parameters
Calling a helper with IDictonary Paramaters:
$HtmlHelper.SubmitButton("Login", $DictHelper.CreateDict("id=btnLogin"))

Categories: MonoRail