Castle Demo AppUpdating our Users

time to read 43 min | 8580 words

Okay, here we go again. One thing that I forgot to mention in the last post is how you access the pages you created. You simply need to use the following URLs:

Before we continue, a couple of words. When I started this series, I had the intention to make it possible to use this with the latest release of Castle. Unfortantely, while writing this series, I encountered a couple of bugs in Brail (and there has been some other changes to MonoRail) that meant that what I write here is relevant to the newest bits. You can get the bits from the Castle Subversion repository.

Just as a note, I noticed that editing Brail documents in VS.Net isn't much fun. I highly recommend using SharpDevelop to edit the views. I support boo and it has much nicer code highlighting than the one you get from VS.Net. That said, I made some modifications to Brail so you could write valid XML documents using:

<?brail

  import Boo.Lang

 

  for i in Builtins.range(3):

    output i

  end

?>

You can still use the <% and %>, but I will use the <?brail ?> style from now on. Note: you can't mix the two in a single page.

After you finish admiring those lovely page, (and curse me for making you download and compile the latest bits) you'll probably want to add some user interaction to the mix, wouldn't you? Let's add editing support for the users.

First, let's us add an edit link to the users page:

<table border="1" width="70%">

     <tr>

           <td>

                <B>Id</B>

           </td>

           <td>

                <B>Name</B>

           </td>

           <td>

                <B>Email</B></td>

    <td></td>

     </tr>

<?brail

     for user in allUsers:

?>

     <tr>

           <td>

                ${user.Id}

           </td>

           <td>

                ${user.Name}

           </td>

           <td>

                ${user.Email}

           </td>

    <td>

      <?brail

        output HtmlHelper.LinkTo("Edit","admin","EditUser",user.Id)

      ?>

    </td>

     </tr>

<?brail

     end

?>

As you can see, I moved to use the <?brail ?> notation. I bolded the new parts. Refresh the page again (no need to recompile anything). You should see a nice "Edit" link from the users list. It doesn't do anything ( and you'll get an error page if you'll try to follow the link.

We'll start by create the EditUser method on the AdminController class, with a single integer as a parameter:

    public void EditUser(int id)

    {

        User user = User.TryFind(id);

        if (user == null)

        {

            RenderView("NoSuchUser");

            return;

        }

        PropertyBag.Add("User", user);

    }

It's a simple method, it gets the id of the user (this is handled by MonoRail automagically) and then try to pull the user from the database. If it doesn't exists, it redirect to the error page. If it does exist, it add the user to the property bag and we're done with the controller. Now it's time to create the views.

The error view is really simple, it's actually just this:

A user with the id: ${Id} could not be found.

Save it to: Views\Admin\NoSuchUser.boo, and now we create the more complex view at Views\Admin\EditUser.boo:

<?brail

     name = ""

     email = ""

     id = ""

     password = ""  

     if IsDefined("user"):

        name = user.Name

        email = user.Email

        id = user.Id.ToString()

        #remember, no getter for the password

        password = "Dummy Password"

     end

    

?>

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

${HtmlHelper.FieldSet('User Details:')}

${HtmlHelper.InputHidden('id', id)}

<table>

     <tr>

           <td>

                ${HtmlHelper.LabelFor('user.Name','User name:')}

           </td>

           <td>

                ${HtmlHelper.InputText('user.Name',name, 50, 50)}

           </td>

     <tr>

     <tr>

           <td>

      ${HtmlHelper.LabelFor('user.Email','User email:')}

    </td>

    <td>

      ${HtmlHelper.InputText('user.Email',email, 50, 50)}

    </td>

     </tr>

     <tr>

           <td>

      ${HtmlHelper.LabelFor('user.Password','Password:')}

    </td>

    <td>

      ${HtmlHelper.InputPassword('user.Password',password)}

    </td>

     </tr>

</table>

${HtmlHelper.SubmitButton('Save')}

${HtmlHelper.EndFieldSet()}

${HtmlHelper.EndForm()}

I'm using a couple of new things here. The HtmlHelper is just a way to pack a bunch of HTML code into reusable form. You can think of the helper as MonoRail version of the WebForms Controlls, although that isn't 100% accurate.

The part in the beginning require some explanation. Brail is partially late bound language. If it can, it detects types of variables during compilation, but if it's not possible, it will wait for runtime to discover what it can about the variables. Because it's possible that the the controller will not pass the user varaible to the view (and we'll see why in a second), we check to see if the variable exists before using it.

One thing that you should notice is that when ever I use the ${...} syntax and I need to pass a string to a method I use the single quote character '. Trying to use the double quote character " will cause an error.

Pay attention to the naming convention of the items in the form, they all start with "user.", and that will be very helpful down the road.

Okay, now run the page (http://localhost:8080/admin/EditUser.rails?id=1) and see what you get. It's functional, if not pretty. But still we have a problem, we now display the users details in an editable way, but we can't save it. Let's build the SaveUser action.

We have several options we can use:

We can do it the hard way, by manually pulling the data out of the Request.Form (relax, I can hear the groans up to here).  Or, we can allow MonoRail to pull the data and pass it to us using the method parameters. We can even allow MonoRail to construct an object out of the fields. But we'll take the really easy way and let MonoRail do nearly all the work.

We need to do the following in order to make it all work seamlessly:

  • Add reference to: Castle.MonoRail.ActiveRecordSupport.dll
  • Add reference to: Castle.Components.Binder.dll
  • Change the base class of AdminController to be ARSmartDispatcherController.

I present to you, in its entirely, the SaveUser() method:

    public void SaveUser([ARDataBind("user", AutoLoad=true)]User user)

    {

        RenderView("GenericMessage");

        try

        {

            user.Save();

            Flash["good"] = string.Format(

                "User {0} was saved successfully.", user.Name);

        }

        catch (Exception e)

        {

            Flash["bad"] = e.Message;

        }

    }

Okay, we have the ARDataBind attribute for the user parameter, and this tells MonoRail that all the parameters that start with "user" belong to this class, and that it is an Active Record class. Since MonoRail has Active Record integration, it simply pull the correct record from the database and give it to us, with the updated values! Then we can decide what to do with it. Here, I'm just saving it to the database and putting a message to the Flash (which is what we use to put transient messages, it's implemented the same way the PropertyBag is, but things on the Flash are usually error or one time messages, things that can appear and disappear.)

We then choose to render the GenericMessage view, which is located at Views\Admin\GenericMessage.boo and looks like this:

<font color="red">

<?brail

 if IsDefined('bad'):

  output bad

 end

?>

</font>

<font color="green">

<?brail

if IsDefined('good'):

  output good

end

?>

</font>

The CSS purist will go crazy, but this is the quickest way to do it :-) Okay, we're done, let's go home.

Oh, I see that you're still here, well, there are a couple of other things that we need to do before we can move on. We got a couple of problems with this approach. We aren't really verifying string lengths, for instance, and a user can change its name to one that already exists. While we can let the database handle this, I really want to make some check in my code as well (and I'm certainly not going to verify email adresses using the database).

We'll go back to our User class and make a couple of changes.

To start with, we need to change the base class of User from ActiveRecord<User> to ActiveRecordValidationBase<User> Then, we need to add a couple of attributes to the Name and Email properties, here they are:

    [Property]

    [ValidateEmail, ValidateLength(50)]

    public string Email

    {

        get { return _email; }

        set { _email = value; }

    }

 

    [Property]

    [ValidateIsUnique, ValidateLength(50)]

    public string Name

    {

        get { return _name; }

        set { _name = value; }

    }

Now it will automatically validate before save. I'll leave building the EditProject site as an excersize for the reader. I meant to talk about creating / deleting users today, but I fear that I needed to refresh myself with a couple of things (and fix / improve Brail in the process).

I'll do that tomorrow, and then you can see just how much reuse we can reap from today's work.

More posts in "Castle Demo App" series:

  1. (03 Mar 2006) ViewComponents, Security, Filters and Friends
  2. (01 Mar 2006) Code Update
  3. (01 Mar 2006) Queries and Foreign Keys
  4. (28 Feb 2006) Complex Interactions
  5. (25 Feb 2006) CRUD Operations on Projects
  6. (22 Feb 2006) Let There Be A New User
  7. (22 Feb 2006) Updating our Users
  8. (20 Feb 2006) Getting serious, the first real page
  9. (20 Feb 2006) MonoRail At A Glance
  10. (20 Feb 2006) The First MonoRail Page
  11. (19 Feb 2006) Many To Many Relations
  12. (19 Feb 2006) Lazy Loading and Scopes
  13. (17 Feb 2006) Active Record Relations
  14. (17 Feb 2006) Getting Started With Active Record