MonoRail - Collocate your Strings

Localization is a pain.  Anybody who says different is a sadist.  It's especially difficult if you are trying to retrofit an app that didn't even try i.e. hard coding all strings in code/views.  Even if I'm not developing an application with localization in mind, I still strive to put all strings in a resx file and never hard code them anywhere.  The recommended method for localizing a standard ASP.Net application was to have a single resx file per page.  Similarly for a WinForms app you would have one resx file per Form.  In a WinForms app this is slightly more understandable since you may need to resize fields to accommodate different languages.  However in web apps the only thing I ever store in a resx file is strings. 

ASP.Net 2.0 introduced the concept of an App_GlobalResource that can be accessed from any page in your site.  This was a step in the right direction because you could easily reuse common strings as needed.  There are cases however when I need to generate a message in my business layer that will ultimately be displayed to the user.  So I end up needing a resx file in my Core assembly, and a resx file in my web app.  Kind of defeats the purpose of a centralized location for strings.  The problem is further compounded by the fact that VS2005 generates a strongly typed wrapper for your resx file that has all strings marked as internal.  So I can't easily put all of my strings in my Core assembly and then reference them in my web app.

I little googling turned up a work around that allows you to generate a wrapper with public properties.  This method worked just fine but I get anal about such things and wanted my publicly generated code to match as closely as possible to what VS2005 would have created for me.  Here is my adapted code to generate a file called Strings.Designer.cs that contains a public wrapper class called Strings in namespace My.Product.Core for my Strings.resx resource file:

<Compile Include="Strings.Designer.cs">
  <AutoGen>True</AutoGen>
  <DesignTime>True</DesignTime>
  <DependentUpon>Strings.resx</DependentUpon>
</Compile>

<ItemGroup>
  <EmbeddedResource Include="Strings.resx">
    <SubType>Designer</SubType>
    <LastGenOutput>Strings.Designer.cs</LastGenOutput>
  </EmbeddedResource>
</ItemGroup>

<!-- Generate Strings.Designer.cs with Public access -->
<Target Name="BeforeBuild" DependsOnTargets="GenStrongTypeResource" />
<Target Name="GenStrongTypeResource" Inputs="Strings.resx" Outputs="Strings.Designer.cs">
  <GetFrameworkSdkPath>
    <Output TaskParameter="Path" PropertyName="SdkPath" />
  </GetFrameworkSdkPath>
  <Exec Command="&quot;$(SdkPath)\bin\resGen.exe&quot; /str:c#,My.Product.Core,Strings,Strings.Designer.cs /publicClass Strings.resx" />
</Target>

The first part of the post is pretty generic, lets see how we can now use our strongly typed Strings class in our MonoRail views.

MonoRail supports Localization in a similar manner to ASP.Net.  You specify which resource file you want to read from and then assign it a friendly name you can use in your view.  Using my technique described above, you essentially bypass all of that.  My view engine of choice is Brail.  To be frank, I'm not sure if you will be able to use this method with NVelocity or any of the other engines.

First off, we need to configure Brail with our Core assembly.  You do so in your web.config file:

<brail debug="true" saveToDisk="false" saveDirectory="BrailGen" batch="false" commonScriptsDirectory="CommonScripts">
  <reference assembly="My.Product.Core" />
</brail>

Next, you add an import statement to your view and then you can use the generated Strings class just like you would in your C# code.  Here is the localized version of the login view I used as an example in my last post:

<%
  import My.Product.Core
%>

${Form.FormTag({'action':'login'})}
<table>
  <tr>
    <td>${Form.LabelFor('username', Strings.Username)}</td>
    <td>${Form.TextField('username', {'class':'required'})}</td>
    <td><div id="advice-username" class="advice" style="display: none;">${Strings.UsernameRequired}</div></td>
  </tr>
  <tr>
    <td>${Form.LabelFor('password', Strings.Password)}</td>
    <td>${Form.PasswordField('password', {'class':'required'})}</td>
    <td />
  </tr>
  <tr>
    <td />
    <td>${Form.Submit(Strings.Login)}</td>
    <td />
  </tr>
</table>
${Form.EndFormTag()}

Nice and easy. Now all of the strings used in your application are centralized in a single file. This makes translation more straight forward and helps prevent duplication of strings in multiple resx files. You could easily extend this and have a Strings assembly that only contains a resx file that could be reused in multiple applications.

MonoRail - Form Validation the Hard Way

Why would you want to do something the hard way?  Well of course you do something the hard way when you can't do something the easy way (unless you are Oren in which case you do everything your way :).  Hammett has several excellent posts on how to do form validation the easy way using the Castle Validator component (screencast and additional thoughts).  The component makes validation dirt simple especially if you are using ActiveRecord.  However the default implementation ties validators to attributes on the model which works 80% of the time. 

For the 20% of the time when you want to get validators from somewhere else one would need to implement a custom IValidatorRegistry.  IValidatorRegistry is responsible for providing all validators for given type and/or property back to the validation framework.  Are you feeling the love?  All you need to do is customize where the validators come from, and you still benefit from the runtime and client side validation generation. w00t!  So, in theory your DatabaseValidatorRegistry could query a...database...to retrieve all validators for a given type and property.  Allowing you to craft an interface where by the end user could modify validation rules at will.  I'm getting a little dizzy just thinking about the possibilities.  I haven't gone there yet but when I do you will be the first to know.

That last paragraph literally popped into my head as I was starting this post.  I actually was going in a different direction for this particular discussion but I just had to put it down on paper.  What I really want to talk about is doing simple client side validation using the client side Validator scripts.  I have a simple login prompt with username and password.  I want to do client side validation to ensure they enter a username and password before submitting the form.  What do you do?  Go.

First things first, you need to install the client side scripts.  It is recommended to do this in your layout so that the scripts are included on all of your pages:

<head>
    <title>MonoRail Validation</title>
    ${Ajax.InstallScripts()}
    ${Form.InstallScripts()}
</head>

What exactly does this do?  Among other things it emits script tags for the Prototype libraries and the Dexagogo 'really easy field validation' libraries.  The next step is to create a form in your view with the necessary fields:

${Form.FormTag({'action':'login'})}
<table>
  <tr>
    <td>${Form.LabelFor('username', 'Username')}</td>
    <td>${Form.TextField('username', {'class':'required'})}</td>
    <td><div id="advice-username" class="advice" style="display: none;">Please enter a Username</div></td>
  </tr>
  <tr>
    <td>${Form.LabelFor('password', 'Password')}</td>
    <td>${Form.PasswordField('password', {'class':'required'})}</td>
    <td />
  </tr>
  <tr>
    <td />
    <td>${Form.Submit('Login')}</td>
    <td />
  </tr>
</table>
${Form.EndFormTag()}

How hard is it to make a field required?  Excruciating!  First the Form.FormTag automatically hooks up the required logic to the validation libraries.  See the 'class':'required' in the TextField and PasswordField elements?  The Dexagogo code uses CSS selectors (similar to Behaviour) to execute validation when the user clicks the Login button.  THAT'S IT!  Now, when you click Login without entering a username, the form will not be submitted and you will be presented with a lovely error message.  The Dexagogo site documents the twelve built in validations you can perform (dates, numbers, email, etc).  If you need more you can specify your own validation callback that will get executed when the form is submitted.  Dexagogo has a number of standard validation messages, but you can easily substitute your own by adding an element to your page with an id of "advice-<fieldName>".  If validation fails, this element will be shown to the user.  I applied a class of "advice" to my element so I could easily style error messages.  One oddity was that I had to inline specify the display: none.  Putting this in my external stylesheet caused the error message to not be shown properly.  The use of CSS selectors also makes it very easy for you to style your required fields (with a yellow background maybe?)  and style your numeric only fields and provide a standard legend to your users.

You can also specify some additional options in the FormTag (Hammett touched on these briefly in his screen cast).  If you want "advice" shown to the user immediately when the leave a field and you want to turn off the default of focusing on invalid fields, you would specify your FormTag like so.

${Form.FormTag({'action':'login','immediate':'true','focusOnError':'false'})}

Finally I'll reiterate the best practice you've heard before, always validate your data server side in addition to any client side validation you may perform.  For your reference here is the fully generated code for the view above:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
  <head>
    <title>MonoRail Validation</title>
    <script type="text/javascript" src="/MonoRail/Files/AjaxScripts.rails?RC3_0006"></script>
    <script type="text/javascript" src="/MonoRail/Files/FormHelperScript.rails?RC3_0006"></script>
  </head>
  <body>
    <form action='/login/login.rails' method='post' id='form1' >
      <table>
        <tr>
          <td><label for="username">Username</label></td>
          <td><input type="text" id="username" name="username" value="" class="required" /></td>
          <td><div id="advice-username" class="advice" style="display: none;">Please enter a Username</div></td>
        </tr>
        <tr>
          <td><label for="password">Password</label></td>
          <td><input type="password" id="password" name="password" value="" class="required" /></td>
          <td />
        </tr>
        <tr>
          <td />
          <td><input type="submit" value="Login" /></td>
          <td />
        </tr>
      </table>
      <script type="text/javascript">
        if (!window.prototypeValidators) prototypeValidators = $A([]);
        var validator = new Validation('form1', {onSubmit:true, focusOnError:true, stopOnFirst:false, immediate:true, useTitles:true});
        prototypeValidators['form1'] = validator;
      </script>
    </form>
  </body>
</html>
«June»
SunMonTueWedThuFriSat
272829303112
3456789
10111213141516
17181920212223
24252627282930
1234567