2008-01-30

MonoRail

I'm working on a new website for work. I've decided to use ECO for the business model due to how much time it saves me. I took a look at the new MVC ASP approach provided by Microsoft recently and was a bit disappointed. There were bugs in some pretty basic errors that would have been an annoyance to code around, and it just didn't feel "ready".

So, I've decided to take another look at MonoRail. I'd already written an ECO implementation for MR in the past but I decided to start the implementation from scratch. This was mainly inspired by the new EcoSpaceManager in ECOIV for ASP .NET. Using an EcoSpaceManager you can easily utilise many instances of different types of EcoSpace in the same page. I decided I would do the same.

Unlike the EcoSpaceManager I haven't gone for unique string values for identifying the EcoSpace instance I want. That approach is good in ASP .NET where you want to bind different components together to generate your HTML but it doesn't really make as much sense when everything you produce is written in code. If you want two instances of the same EcoSpace you can just use EcoSpaceStrategyHandler.GetEcoSpace().

Anyway, on to the detail:

The first thing I have done is to specify EcoSpace and EcoSpaceProvider settings as reflection attributes on the class (controller) and method (action). Like so

[UseEcoSpacePool(true)]
public class AccountController: EcoSmartDispatcherController
{
  public void Index()
  {
  }

  [UseEcoSpacePool(false)]
  public void SomethingElse()
  {
  }
}
In this example the EcoSpace pool will be used for all actions (methods) except SomethingElse which explicitly says not to use it.

If you want to specify EcoSpace type specific settings in your web app then this is possible too:

[UseEcoSpacePool(true)]
[UseEcoSpacePool(typeof(MyEcoSpace), false)]
public class AccountController: EcoSmartDispatcherController
{
  public void Index()
  {
  }

  [UseEcoSpacePool(typeof(MyEcoSpace), true)]
  public void SomethingElse()
  {
  }
}
In this example the default is to use the EcoSpace pool unless you are retrieving an instance of MyEcoSpace in which case it wont be used. However, if the action being invoked is SomethingElse() then retrieving an instance of MyEcoSpace will use the pool. The order of priority is as follows:
  1. Apply settings on the class that are not specific to an EcoSpace type.
  2. Apply settings on the method that are not specific to an EcoSpace type.
  3. Apply settings on the class that are specific to an EcoSpace type.
  4. Apply settings on the method that are specific to an EcoSpace type.
So the order of priority is that Method settings override Class settings, and then EcoSpace type specific settings override non specific settings.

Pretty versatile eh? But how do you get an instance of an EcoSpace? Well, that's pretty simple too!
public void Index()
{
  MyEcoSpace myEcoSpace = GetEcoSpace<MyEcoSpace>();
  //This will return the same instance
  MyEcoSpace myEcoSpace2 = GetEcoSpace<MyEcoSpace>();
}
Any EcoSpaces requested in this manner will automatically be "released" immediately after the method finishes executing so you don't need to worry about it at all. I say "released" because it will either be Disposed, stuffed into the session, or returned to the pool based on your EcoSpaceProvider settings for this class/method/EcoSpace type.

That's not the end of the story though. You can bind instances of your objects directly to HTML and back. To do this you need to identify your business class with a reflection attribute "EcoDataBind", like so
[AllowDeactivateDirty(true)]
public void Create(
  [EcoDataBind(typeof(MyEcoSpace), "Product", CreateIfNoObjectId=true)]Product product)
{
  PropertyBag["Product"] = product;
}
Here I have stated that it is okay to Dispose the EcoSpace instance if it contains dirty objects at the end of this method. The method itself consists of a single parameter named "product" of type "Product". The EcoDataBind might look a bit overwhelming but it says this
  1. The EcoSpace type you need to home the Customer object is MyEcoSpace.
  2. The prefix in the form to look for is "Product", so when you see <input name="Product.Name">> MonoRail knows that the value should go into the Name property of this product.
  3. If there is no ExternalId in the form identifying the object to fetch from the data storage before updating then a new instance should be created.
As a result when you run your app and go to localhost/account/create you will see that the product method has a new instance in there. When the user posts the form back you will again see an instance but containing the updated values. What does the HTML look like? I have used the Brail view engine and HTML helpers to output the HTMl I need. This allows you to use whatever HTML you like but then easily add the <input> etc based on your current object.

${HtmlHelper.Form('create.rails')}

  ${EcoFormHelper.PersistedExternalId('Product.ExternalId', Product)}

  Name : ${HtmlHelper.InputText('Product.Name', Product.Name)}

  Current version number : ${HtmlHelper.InputText('Product.CurrentVersionNumber', Product.CurrentVersionNumber)}

  ${HtmlHelper.SubmitButton('Save')}

${HtmlHelper.EndForm()}
I have split the lines up a bit to make them easier to visually separate.

First an EcoFormHelper is told to output a hidden input named Product.ExternalId for the product we set in the C# method (see PropertyBag["Product"] = product). EcoFormHelper.ExternalId will output the ExternalId for the object, PersistedExternalId will only output if the object is not new, this is useful in situations like this when the object was disposed of with the EcoSpace it belong too and we can just create a new instance.

Next the HtmlHelper gives us an <input> named "Product.Name" and its value is set to whatever is in the Product's Name property. The same is done for CurrentVersion.

A Submit button is then generated so that the user may post their changes.

Summary
Well, this little example shows that I can implement a nice clean MVC style approach to writing web apps with ECO and not have to worry about constructing EcoSpace instances in code, fetching objects from the data storage and so on manually; everything is done for me.

3 comments:

Dmitriy Nagirnyak said...

Hi Pete,

Great work!
And I agree with you about MS ASP.NET MVC - it looks cool and has very good Routing support, but is too "wet" for now.

I have a couple of suggestions/question.
Will continue on the NG

Cheers.

franklt69 said...

Peter, I read tha MonoRal run over Mono, so I think I can do an app using monorail and deploy it on linux, do you are testing the combination monorail + ecoiv on linux?

regards
Frank

Peter Morris said...

I don't have Linux so I don't know.

I heard ECO didn't run on Mono, but I have no idea whether or not that is still the case. You should try it :-)