Oren Eini

CEO of RavenDB

a NoSQL Open Source Document Database

Get in touch with me:

oren@ravendb.net +972 52-548-6969

Posts: 7,546
|
Comments: 51,161
Privacy Policy · Terms
filter by tags archive
time to read 59 min | 11664 words

In this post, I'm going to explain how to secure the administration section of the site. Since the pages in our application do not map to a directory structure on the server, the ASP.Net default security isn't going to help us (I'm not entirely sure about this, though).

Because of the way that the MonoRail is built, we often can make security decisions about the all the actions in a controller. Such is the case with the AdminController, only administrators should be able to access this page. Currently, we don't have the notion of an admin in the application, so we will need to add that as well.

We want to think about this for a minute, do we want a seperate page for login, or do we want to have a login box in every page? I think that we want a login box in every page, which can either display the Enter User and Password, or the currently logged on user. We can add it to the layouts, and then it will be seen in all the pages that belongs to this layout. But, we have multiply layouts in the application. Right now we have two of those, but we may have more in the future. So we can't just put it in the layout.

If I was using ASP.Net, I could've created a control (or used a pre-built one). In MonoRail, this is called a ViewComponent, and we'll build one in a few minutes. Just to note, in Brail, we can often just use a Common Script method, but because ViewComponents has access to MonoRail state, it's often simpler to just use them when you need to access various parts of MonoRail. In this case, we need to determain if the user it logged on or not, so we'll build a ViewComponent for this. Here is the code for the LoginComponent:

public class Login : ViewComponent

{

    public override void Initialize()

    {

        Flash["user"] = this.RailsContext.CurrentUser.Identity;

    }

    public override void Render()

    {

        if (this.RailsContext.CurrentUser.Identity.IsAuthenticated==false)

            RenderView("Login");

        else

            RenderView("LoggedOn");

    }

}

We put the current user identity in the Flash on intialization (so the view can make use of it, if applicable), and when we render the control, we choose which view to render, the Login control, or the LoggedOn notification. If you are developing using the WebDev.WebServer, you already has Windows Authentication on, we want to disable that for this app (I want to show how we can build our own). You can disable Windows Authentication by updating the Web.Config file to include this little snippet.

<system.web>

     ...

<authentication mode="Forms"/>

...

</system.web>

A couple of things about ViewComponents, they are like tiny controllers, but they should be focused on doing spesific stuff, usually providing some sort of a all around service, like this login control. This is a way where you can still get the seperation of concerns that I like so much in MonoRail, and get functionality that can cut through all the layers of the application without breaking the design.

Now for the details, the views for a component are located on Views\Components\<Component Name>, and the default view for a component is Default.boo (no surprise here, sorry). So the Login view is located at Views\Componenets\Login\Login.boo, and looks like this:

${HtmlHelper.Form('/Home/AuthenticateUser.rails')}

${HtmlHelper.FieldSet('Login:')}  

<table>

      <tr>

            <td>${HtmlHelper.LabelFor('userName','Username:')}</td>

            <td>${HtmlHelper.InputText('userName','')}</td>

      </tr>

      <tr>

            <td>${HtmlHelper.LabelFor('password','Password:')}</td>

            <td>${HtmlHelper.InputPassword('password')}</td>

      </tr>

</table>  

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

<p>

      ${HtmlHelper.SubmitButton('Login')}

</p>

${HtmlHelper.EndFieldSet()}

${HtmlHelper.EndForm()}

Nothing much going here, we have a user and password fields, and we direct them to the AuthenticateUser action. The LoggedOn view is located at Views\Components\Login\LoggedOn.boo and is defined so:

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

<table>

      <tr >

            <td>${HtmlHelper.LabelFor('userNameLabel','Username:')}</td>

            <td>${HtmlHelper.LabelFor('userName',user.Name)}</td>

      </tr>

      <tr>

          <td>${HtmlHelper.LinkTo('Logout','home','logout')}</td>

      </tr>

</table>

${HtmlHelper.EndFieldSet()}

Again, this is nothing new, just showing the user name and a logout link. Now, let's add that to the Home layout (located at Views\Layouts\Home.boo), and edit it so it will look like this:

<!DOCTYPE xhtml

PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html>

<body>

<table>

      <tr>

            <td>

                  ${ChildOutput}

            </td>

            <td width="20%">

                  <?brail component Login ?>

            </td>

      </tr>

</body>

</html >

(I hear the CSS Designers' League is on the lookout after me because of this series :-D). As you can see, we only need to specify which comonent we want, and that is it. We also need to make the same change to the Admin layout. Compile the application and point your browser to: http://localhost:8080/home/default.rails, here is how it looks like on my system:

Okay, we now have a login control, let's put it to good use. We'll add the Authenticate User action to the HomeController, but before we can do that we need to consider some design implications of this change. We will probably need to give the user some error message if they couldn't login, but we already wrote that when we did the AdminController. I get the shakes when I see duplicate code, so we need to do a couple of things here, we need to make the GenericMessage view aviable to all the controllers in the application, and we probably want to make the RenderMessage() method accessible to the HomeController as well.

Well, a view is really something that is relevant for a single controller, but luckily for us, we don't need to duplicate the view, since MonoRail has the notion of a Shared View. We need to move GenericMessage.boo from Views\Admin\ to Views\ (to make it visible for other controllers).

Now, we need to make RenderMessage() accessible for the HomeController. I choose to do this with Extract Base Class, and create the following class, which both AdminController and HomeController now inherits from:

public abstract class MythicalBugTrackerControllerBase : ARSmartDispatcherController

{

    protected void RenderMessage(string nextActionText, string nextAction)

    {

        PropertyBag["nextActionText"] = nextActionText;

        PropertyBag["nextAction"] = nextAction;

        RenderSharedView("GenericMessage");

    }

}

This is an abstract class that inherits from ARSmartDispatcherController (which in turn inherits from SmartDispatcherController, so we get the nice features of being able to get real method parameters instead of unpacking the request parameters manually). Note that we are using RenderSharedView() to render GenericMessage. Another thing to note is that I belong to the school of thought that is not afraid of long class names.

It's important to note that I'm wrapping the render view call with a method that make sure that the mandatory parameters are set. This ensure that I won't forget to add a parameter and cause my view to throw an exception. I find that this works quite well to create an interface between my views and te controller.

Now, we need to write the code that will handle the security infrastructure for the application. A couple of notes on that before we continue. This is what I call Reasonably Secured application, which means that it's not easy to by-pass the security, but it's not impossible. The security scheme I'm going to show you is vulnerable to attacks such as Man In The Middle and Cookies Replay. If you want true security, you need an expert. If you work in Israel, I can refer you to a superb expert in this field.
Our bug tracking application isn't important enough to make it truely secured (which take more than a bit of work), but we can make it reasonably secured, and I think that this would be enough in this case.

We'll try to take advantage of as many features that ASP.Net provides us as possible, so we'll start with making our User class implement IPrincipal, which just mean that we need these two methods:

public class User : ActiveRecordValidationBase<User>, IPrincipal

{

  ....

 

#region
IPrincipal Members

 

    public IIdentity Identity

    {

        get

        {

            return new GenericIdentity(Name, "Mythical.Bug.Tracker.Authentication");

        }

    }

 

    //TODO: Add support for this later

    public bool IsInRole(string role)

    {

        return false;

    }

 

    #endregion

  ....

}

Right now we don't offer roles, so we always return false (we might add roles later), and the Identity is just the generic identity. Now that we have an IPrinicpal, we can start implementing the login features, here is the Authenticate User method:

    public void AuthenticateUser(string userName, string password, string currentAction)

    {

        User user = User.FindUserByLogin(userName, password);

        if (user == null)

        {

            Flash["bad"] = "User / Password mismatch";

            RenderMessage("Back to home page", "default");

            return;

        }

        AddAuthenticationTicket(user);

 

        Redirect(currentAction);

    }

    private void AddAuthenticationTicket(User user)

    {

        FormsAuthenticationTicket ticket = new FormsAuthenticationTicket(1, user.Name,

            DateTime.Now, DateTime.Now.AddMinutes(20), false, user.Id.ToString());

        string cookieValue = FormsAuthentication.Encrypt(ticket);

        this.Context.Response.CreateCookie(FormsAuthentication.FormsCookieName, cookieValue,

            DateTime.Now.AddMinutes(20));

        this.Context.CurrentUser = user;

    }

We are doing a couple of cool things here. We start by trying to find a user by its name and password, if we can't find it, we send the user to the error page. If we do find a user, we create an authentication ticket for this user (using the pre-existing Froms Authentication in ASP.Net), encrypt it, and store it in a cookie. A couple of points before moving on:

  • Because we are using the existing support in ASP.Net, we saved a whole lot of coding that we may have had to do.
  • We also do here is replace the current user, this is needed so anything that queries the current user in this request (such as the Login component) will recognize the user that just logged in.
  • Notice that the cookie is encrypted and hashed, which means that it's not likely that someone could tamper with our cookie. This is important, since it means that when we get the cookie back, we can rely on its contents (but only if they pass decryption and checks, of course).
  • Since the Authenticate view doesn't have any UI to display, we call the Default() method (which push some parameters for the view to handle) and then specify that we want to use the Default view. It's very easy to do this kind of switching, since we are dealing with the same object, and not another page, which requires some coordination between classes. The only thing that I needed to to support this was to add a call to RenderView("Default") in the Default() method, so the correct view will be rendered, even if we are calling it from another action.

Now that we have an encrypted cookie on the client, let's see how we handle the authentication on the next request. We do this by hooking the AuthenticateRequest event in MythicalBugTrackerApplication. The code that does the check is here:

    void MythicalBugTrackerApplication_AuthenticateRequest(object sender, EventArgs e)

    {

        HttpCookie cookie = Request.Cookies.Get(FormsAuthentication.FormsCookieName);

        if (cookie == null)

            return;

        int id = GetUserId(cookie);

        Model.User current = Model.User.TryFind(id);

        if (current == null)

        {

            //This means that we've a cookie for a user that has been removed, we'll

            //remove the cookie and redirect to the default page, if the user will

            //try to log in again, they will get the usual message, and then it is

            //the IT problem.

            RemoveAuthCookieAndRedirectToDefaultPage();

        }

        Context.User = current;

    }

We get the cookie from the incoming request, and send it to the GetUserId() method (which we'll see in a few seconds). Then we take the user id and try to get the matching user. If the user exists, then we set the Context.User property to this user (which we can do because our User implements IPrincipal). It's possible that the user does not exist (it may have been removed, for instance), so we handle that be removing the authentication cookie and redirecting to the default page. We now have authentication working for our app. Now, let's see some of the details:

    private int GetUserId(HttpCookie cookie)

    {

        try

        {

            FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt(cookie.Value);

            return int.Parse(ticket.UserData);

        }

        catch (ArgumentException ae)

        {

            logger.ErrorFormat(
                "A possible hack attempt, got an invalid cookie value: '{0}' from {1}",

                cookie.Value, Request.UserHostAddress);

            //Somebody tried to mess with the cookie, could be a hacker or transimission

            //failure, we'll remove the cookie and send them to the default page.

            RemoveAuthCookieAndRedirectToDefaultPage();

            return -1;//will never reach here, the previous method will abort the thread

        }

    }

Most of this method is error handling, but basically what happens here is that the cookie in decrypted, and then the id we put there is extracted and returned. The interesting part is the error handling. It's possible that the Decrypt() method (or the Parse(), for that matter) will throw, and in this case, it is most likely a hacker tampering with the cookie. We log the error and then remove the cookie and redirect to the default page in this case. The thinking here is that it may be a transmission error, and anyway, we don't want to provide any information to the hackers (even something like; "modifed cookie values found, kicking you out" is giving away too much information.) For completeness sake, here it the RemoveAuthCookieAndRedirectToDefaultPage() method:

    private void RemoveAuthCookieAndRedirectToDefaultPage()

    {  
 
       FormsAuthentication.SignOut();

        Response.Redirect("/home/default.rails", true);

    }

One thing that you need to pay attention to here, we are not using the MonoRail API here, but the ASP.Net ones. This is because we want MonoRail to get the request with the current user already set, and this is still too early in the request pipeline for MonoRail to take over.

So, we now have a way to login a user, and a way to authenticate a logged in user on the next request, we need two more things before we can say that the security part of the application is complete. The first thing is to allow a user to log out, and to make sure that only authorized users can access the administrators section.

We'll start by creating the Logout functionality, since this one is very simple.

    public void Logout()

    {

        FormsAuthentication.SignOut();

        Redirect("home", "default");

    }

We remove the cookie from the user and redirect to the default page. Now, let's implement security for the administration section. At this point, all we want to achieve is to allow only authenticated users to access thos pages. We will do this with a Filter. A Filter is a wrapper around a request, which can do action for the request. Here is our authentication filter:

public class AuthenticationFilter : IFilter

{

    public bool Perform(ExecuteEnum exec, IRailsEngineContext context, Controller controller)

    {

        if (context.CurrentUser == null || context.CurrentUser.Identity.IsAuthenticated == false)

        {

            context.Response.Redirect("home", "default");

            return false;

        }

        return true;

    }

}

It simply checks to see if you are authenticated, and if not, kicks you back to the home page. Now we need to integrate it with the AdminController, this is done by specifying this attribute:

[Filter(ExecuteEnum.BeforeAction, typeof(AuthenticationFilter))]

public class AdminController : MythicalBugTrackerControllerBase

This is it, we have now secured the administration section, only authenticated users will be able to access those parts now. As I mentioned, this is reasonable security, but if we deal with sensitive data, or mission critical operations, it's not good enough. In those cases, you want to run the sensitive sections of your site on SSL, and have a full security audit before you go live.

Well, that was a long one. I hope that you're following, since there are still more to come (here is the non-complete, totally random list):

  • Tests! Tests! Tests!
  • Personalization
  • Roles
  • Handling exceptions
  • Actually writing the Submit Bug page.
  • Complex searches using Active Record
  • Mitigating Cross Site Scripting Attacks
  • Sending Emails
  • Thinking about Castle

In the meantime, I realized that I forgot to give the urls for you to check what is happening. I'm sure you figured them out, but here are the usable entry points into the application, for reference:

  • http://localhost:8080/admin/Projects.rails
  • http://localhost:8080/admin/users.rails
  • http://localhost:8080/admin/index.rails
  • http://localhost:8080/home/default.rails

You can get the application here.

time to read 1 min | 89 words

I've updated the code that is avialable. Now I also bundle the parts of Castle that you need in order to run it, so you will have easier time build and testing this code. All the binaries you need can be found in the Deps directory, you'll need to change the references to point to that directory. You will also need SQL Server or SQL Express for the database, as well as .Net 2.0.

You can get the new release here (1.2Mb).

time to read 16 min | 3103 words

Okay, one thing that I'm sure that you have noticed is that we have a bug in the DeleteUser method. The problem is that we don't check for foreign keys violations, and that will cause problems down the road (someone tries to delete a user that is assigned to a project, and he suddenly stares at an error page asking "But what did I do?"

Let's try to prevent it before we get a bug report about it. First, we need to make a couple of policy decsisions. There are two ways that a user can be attached to a project. Either the user is  the default assignee, or he is assigned to the project. From my perspective it's perfectly fine to delete a user if he is assigned to a project, the assignment to the project should just go away, but it's bad to remove a user that is the default assignee of a project.

How do I do this? When we first defined the relationship between users and projects, I decided that the class that should be responsible for maintain the association between the objects would be Project class. Because of this, we defined the Projects as the ones responsible for the connection. This ensure that the association is managed properly (otherwise you might get into a case when you save both the user and the project, and each tries to update the same association, which is a waste at best, and can be dangerous at worst.

Because of that, we will not instruct Active Record to deal with that, but do it manually, and here is the result:

    public void DeleteUser([ARFetch("id")]User user)

    {

        if (user == null)

        {

            RenderView("NoSuchUser");

            return;

        }

        if (AssertUserIsNoDefaultAssigneeInAnyProjecT(user)==false)

        {

            return;

        }

        user.Projects.Clear();

        user.Delete();

        RedirectToAction("Users");

    }

As you can see, we Clear() the Projects collection before we delete the user, and that is all. Active Record takes care of the rest.

Now to the AssertUserIsNoDefaultAssigneeInAnyProject() method:

private bool AssertUserIsNoDefaultAssigneeInAnyProject(User user)

    {

        Project[] projectsUserIsDefaultAssigneeIn = Project.FindAll(Expression.Eq("DefaultAssignee", user));

       

        if (projectsUserIsDefaultAssigneeIn.Length > 0)

        {

            List<string> names = new List<string>();

            Array.ForEach(projectsUserIsDefaultAssigneeIn,

                delegate(Project project) { names.Add(project.Name); });

 

            Flash["bad"] = string.

                Format("User {0} is the default assignee for {1}, and so cannot be deleted",

                    user.Name,

                    string.Join(",",names.ToArray()));

            RenderView("GenericMessage");

            return false;

        }

        return true;

    }

 

We ask to get all the projects where this user is the default assignee. We don't deal with SQL, or need to pass the correct Id, or anything like this. We just tell Active Record, "give me all the projects where this user is the default assignee". This is one of the main reasons I like Active Record so much.

We then unpack the projects names to an array, and tell the user he can't delete a user that is the default assignee in a project (we also tell him what the projects are, which would save some frustrations in the future).

This is it, we solved the problem.

But wait, we have another problem, again with users. The problem is with the password field. If the user doesn't change it, it's set to the default "Dummy Password", which is probably not a good thing. Let's see how we can fix it.

First, we remove the "Dummy Password" default from the view, and leave it with an empty password (this will make it easy afterward to tell whatever the user changed the password.

Then, we change the SaveUser() method to this:

    public void SaveUser(

        [ARDataBind("user", AutoLoadUnlessKeyIs = 0,

            Exclude="Password")] User user)

    {

        RenderMessage("Back to Users", "Users");

        try

        {

            string pass = Request.Params["user.Password"];

            if (string.IsNullOrEmpty(pass) == false)

                user.Password = pass;

            user.Save();

            Flash["good"] = string.Format(

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

        }

        catch (Exception e)

        {

            Flash["DataBindErrors"] = user.ValidationErrorMessages;

            Flash["bad"] = e.Message;

        }

    }

The only interesting thing here is the Exclude, where we tell Active Record to not set the Password property. We then manually get the value, check if it is not empty and if it is not, set the password.

time to read 61 min | 12194 words

Castle Demo App: Complex Interactions

We have CRUD operations for users and projects. The next step is to add a way to assign / remove users from a project. We can do it both for users and projects, but this is just overly complex. We're going to allow a project edit page to add or remove users, and that is all.

I'll be the first to admit that I'm not very good with JavaScript, and I've practically no knowledge in the Ajax capabilities of MonoRail (which are based on Prototype library). I'm just going to stubmle around in this post, and see if I can make something work. Do ignore any screaming that you may hear in the background, this is just me hating browsers :-)

The reason that I'm so late with this post is that I run into a couple of problem with Ajax that weren't easy to solve (or even find). Just to put things in perspective, I wrote the code for this post in an hour and a half, and that included looking at MonoRail source, finding a bug and fixing it. In contrast, it took me much longer to write / debug / fix the problems in the java script parts. I think that this is mostly my fault, though, for not knowing the DOM and the environment well enough. Anyway, enough chit-chat, we've an application to write!

We start by adding this snipper to the head of Views\Layout\Admin.boo:

<head>

${AjaxHelper.GetJavascriptFunctions()}

<title>Administrator Section</title>

</head>

This simply add the correct javascript refeferences (<script> tag) so we could use the ProtoType functions. What I'm going to do next involve a bit of Ajax and some understanding of the tools that MonoRail gives me. I'll explain everything in a couple of minutes, but for now, here is the new EditProject method:

    public void EditProject([ARFetch("id")]Project project)

    {

        AddAllUsersToPropertyBag();

        ValidateObjectToSendToView("project", "Projects", project);

        User[] allUsers = User.FindAll();

        PropertyBag["assignedUsers"] = new User[0];

        PropertyBag["unassignedUsers"] = allUsers;

        if (project == null)

            return;

        ICollection<User> assignedUsers = project.Users;

        User[] unassignedUsers = Array.FindAll(allUsers, delegate(User user)

        {

            return assignedUsers.Contains(user) == false;

        });

        PropertyBag["assignedUsers"] = assignedUsers;

        PropertyBag["unassignedUsers"] = unassignedUsers;

    }

This requires some explanation, I believe. The first two lines are the same as before, but the rest of it is all new. The idea is to pull all the users from the database, and then filter them based on those not already assigned to the project. We then pass the assigned and unassigned users to the view. The two lines just after User.FindAll() are there because we need to give the view something to display if this is a brand new project.

Now, let's see what we need to do to the view in order to make it support our brand new interface, here we have quite a bit of word, as a matter of fact. Because I made so many changes, I'm going to show each part of the page and then provide explanation on what it does. It's a lot of code, but it is not complicated one:

<?brail

def outputUsersList(usersList, addEnabled, removeEnabled):

     index = 0

     addStyle = ""

     removeStyle =""

     addStyle = 'display:none;' if addEnabled == false

     removeStyle = 'display:none;' if removeEnabled == false

    

     for user in usersList:

           userClient = "user_id[${user.Id}]"

           moveToAssigned = "addToProject($('${userClient}'))"

           moveToUnassigned = "removeFromProject($('${userClient}'))"

           output "<tr id=${userClient} userId='${user.Id}'><td>"

           output AjaxHelper.LinkToFunction('&lt;&lt;', moveToAssigned, {'id':userClient+'.addToProject', 'style':addStyle})

           output "</td><td>${user.Name}</td><td>"

           output AjaxHelper.LinkToFunction('&gt;&gt;', moveToUnassigned, {'id': userClient+'.removeFromProject', 'style':removeStyle})

           output "</td></tr>"

           index++

     end

end

 

name = ""

defaultAssigneeId = -1

id = "0"

if ?project:

   name = project.Name

   defaultAssigneeId = project.DefaultAssignee.Id

   id = project.Id.ToString()

end

 

?>

Notice the outputUsersList() method, this one is one of the reasons I love Brail, it allows me to do Extract Method refactoring for HTML. What this method does is very simple, it gets a list of users, and two flags telling it whatever to show or hide the add / remove functionality. You can see a couple of things here, we are taking advantage of the fact that Boo (which is the base langauge for Brail) has strong inference capabilties, so we don't bother with specifying types, in fact, this method declaration looks very much like the declaration we will see in a moment, in the java script code. (Although I find Boo much easier to work with than Javascript, of course.)

We output some HTML to the client, which isn't very interesting (and vaguely resembles CGI code), but we have a call there to AjaxHelper, which I'm going to assume that you're interested at. The method we use is LinkToFunction(), which just means that MonoRail generates a link for us, and will call a (local) java script function when the link is clicked. The first parameter is the text of the link, the second is the function to call (including its parameters), the third is used to pass a dictionary of attributes for the link (and one of them is display=none, which is used to hide the link if it's not needed.

One thing to notice here is the way I'm using the javascript. First, "$()" is a function call that I get from Prototype, it'll get me the element that I need without needing to do stuff like document.getElementById(), etc. Second, I'm passing this function the id for whole <tr> element. We'll see how it makes our life easier in a couple of minutes, since it means that I can just move stuff around without worrying how it looks. Third, notice that I'm naming all the links the same way, the container id plus "addToProject" or "removeFromProject", this will allow me to get the correct link easily, without traversing the DOM to find the right one. Because I know the user id is unique, I know that both the <tr> id and the links ids will be unique in the page. One other thing that I am doing here is to add a userId attribute to the <tr> element, I'll be using this attribute in the Ajax code, to get the user I'm working on.

I think that the parts after the method require no explanation, we've already seen them before. Before I will show the Javascript, we need to take a look at the page, and see how it's laid out, otherwise you wouldn't be able to make head or tails from the scripts, so here it is:

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

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

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

<table>

     <tr>

           <td>

                ${HtmlHelper.LabelFor('project.Name','Project name:')}

           </td>

           <td>

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

           </td>

     <tr>

     <tr>

           <td>

      ${HtmlHelper.LabelFor('project.DefaultAssignee.Id','Default Assignee:')}

    </td>

    <td>

           ${HtmlHelper.Select('project.DefaultAssignee.Id')}

           ${HtmlHelper.CreateOptions(allUsers, 'Name','Id', defaultAssigneeId)}}

           ${HtmlHelper.EndSelect()}

    </td>

     </tr>

</table>

<p>Users Assignments:</p>

<div  id='assignedUsersInputs'>

<?brail for user in assignedUsers: ?>

     ${HtmlHelper.InputHidden('project.Users', user.Id)}

<?brail end     ?>        

 </div>

<table>

  <tr>

    <th style="BORDER-BOTTOM: #f96 1px dashed; BORDER-RIGHT: #f96 1px dashed">Users assigned to this project:</th>

    <th style="BORDER-BOTTOM: #f56 1px dashed; BORDER-LEFT: #f56 1px dashed">Users not assigned to this project:</th>

  </tr>

  <tr>

    <td style="BORDER: #f96 1px dotted">

      <table id="assignedUsersTable">

        <tbody>

        <?brail

                outputUsersList(assignedUsers, false, true)

        ?>

        </tbody>

      </table>

    </td>

    <td style="BORDER: #f56 1px dotted">

      <table id="unassignedUsersTable">

           <tbody>

           <?brail

                outputUsersList(unassignedUsers, true,  false)

           ?>

           </tbody>

      </table>

    </td>

  </tr>

</table>

<p>

${HtmlHelper.SubmitButton('Save')}

</p>

${HtmlHelper.EndFieldSet()}

${HtmlHelper.EndForm()}

The first part isn't interesting, since we already seen it before, what we will look at is everything below the "Users Assignments"  paragraph. We have the assignedUsersDiv, where we put all the users assigned to this project in hidden inputs. Notice that all those hidden input fields are named the same: "project.Users", this will become important when we'll save the project.

We then define a table, and use the method outputUsersList() to output the correct list to the page. If we would run the page now, we'll see something like this:

I am not a designer, and the HTML above has some hardocded values instead of using CSS, but I'm not doing this series to show you how to build pretty sites. :-) Do you see the >> and << signs near the users? This will allow us to simply move a user to or from the project.

Assume for a moment that this page was written in ASP.Net, and that a post-back is required to move a user on/off the project. I now want to add three users and take one away, how much time will this take me? About a minute or two, assuming that the connection and server are fast. And that is for doing no processing whatsoever excpet move a text box from one spot to the other.

The way we are doing this is much more user friendly (at the cost of being much less developer friendly, but that is the usual balance). Okay, we've seen the HTML code and the way it looks like, now let's move on to everyone's favoriate buzzward, Ajax:

<script lang="javascript">

     function addToProject(userTag)

     {

           Element.remove(userTag);

           $('assignedUsersTable').tBodies[0].appendChild(userTag);

           toggleLinks(userTag);

           var i =0;

           var hidden = document.createElement('input')

           hidden.type='hidden';

           hidden.value = userTag.getAttribute('userId');

           hidden.id = hidden.name = 'project.Users';   

           $('assignedUsersInputs').appendChild(hidden);

     }

     function removeFromProject(userTag)

     {

           Element.remove(userTag);

           $('unassignedUsersTable').tBodies[0].appendChild(userTag);

           toggleLinks(userTag);

           for(var node = $('assignedUsersInputs').firstChild;node !=null; node = node.nextSibling)

           {

                if(node.nodeType != 1) //input

                     continue;

                if(node.value == userTag.getAttribute('userId'))

                {

                     Element.remove(node);

                     break;

                }

           }

     }

     function toggleLinks(userTag)

     {

           Element.toggle($(userTag.id +'.addToProject'),

                $(userTag.id +'.removeFromProject'));

     }

</script>

We'll start with the simplest function, toggleLinks(), it takes a single element, and using its id, it find the related addToProject and removeFromProject links, and toggle their visibility. The Element class is part of Prototype, and provides many useful methods.

The addToProject() function will remove the element it gets from the page and add it to the assignedUsersTable and switch the links state. Then it creates a hidden input field and stores it in the originally named assignedUsersInputs <div>. This <div> is used merely as a place holder in the DOM for all the hidden fields. One super important thing to remember. An input field you create with javascript must have a name, otherwise it won't be sent to the server! I lost more than a little bit of time over this. (Thanks, Jeff!)

We're setting all the input fields to use the same name and id. The id I don't care much about, but it's important that they'll all have the same name, since we'll use that in our controller code.

The removeFromProject() function does pretty much the same thing, physically move the element from one container to another, toggle the links and then search through the children of assignedUsersInputs for the hidden field that match the user, so it can remove it.

Okay. This is it. We're done with all the client code! I'm thinking about throwing a party just for that...

Now to the SaveProject() method, which should now handle saving the assigned users for this project. How hard do you think this will be? Let's find out:

    public void SaveProject(
        [ARDataBind("project", AutoLoadUnlessKeyIs = 0)]Project project,

        [ARFetch("project.DefaultAssignee.Id",
                       Create = false, Required = true)] User defaultAssigneee,

        [ARFetch("project.Users")]User[] assignedUsers)

    {

        RenderMessage("Back to Projects", "Projects");

        try

        {

            if (project.DefaultAssignee == null)

                project.DefaultAssignee = defaultAssigneee;

            //Remove all assigned users from the project, since the autority

            //on who is assigned to the project is now the assigned users in the page.

            project.Users.Clear();

            Array.ForEach(assignedUsers,

                delegate(User users) { project.Users.Add(users); });

            project.Save();

            Flash["good"] = string.Format("Project {0} was saved successfully.", project.Name);

        }

        catch (Exception e)

        {

            Flash["DataBindErrors"] = project.ValidationErrorMessages;

            Flash["bad"] = e.Message;

            Flash["Debug"] = e.ToString();

        }

    }

That is a lot of attributes, I head you say, and I agree. But it's better than saying, that is a lot of code...  Those attirbutes saves quite a bit of time, and if you don't like them, you can access their functionality without losing anything. Personally, I like this ability, but I know that opinions about this are divergent.

What do we have here, then? We have the project and defaultAssignee parameters, which we had before, and a new one, an array of assignedUsers. I talked about ARFetch in previous posts, it take an id and gives you the matching object. It also works on arrays, so I don't need to do anything to get the list of users the admin assigned to this project. ARFetch knows to do this because all those ids were named project.Users, so it can just grab all the parameters with this name and load them by their ids.

One thing that I have to do manually, however, is to add the assigned users to the project. MonoRail will not do this for me, and that is a Good Thing. MonoRail doesn't know about the semantics of the operation, in this case, I want to remove all the users already assigned, and add the ones the admin assigned now, but what happens when I want something else? MonoRail can't tell, so it's giving us the live objects, and leave the last step for the business logic to handle.

As you can see, it's not much of a burden, we clear the Users collection, then add all the users the admin specified to the project and save the project. That is nearly it. We need to pass the assignedUsers and unassignedUsers to the view from the CreateProject action as well. Since I'm going to be duplicating code here, I'm going to refactor it out and put it in AddAllUsersToPropertyBag(), since this is the common dominator between EditProject() and CreateProject().

Okay, that was a long post. Just a couple of things before I wrap things up, just think of how much work we did in this page. We've an Ajax interface, we load and save complex data to the database, including managing many to many relationships. I may be a partial observer, but I think that we did all that with a minimum of fuss, and not much complexity.

Next, we will talk about some latent bugs and adding some more validation to the application. Then, it'll be time for the Big Security Review. I got another post coming soon, so I'll update the binaries on the site when I post that one, and not for this.

My Projects | BrailOpen Source | CastleProgrammingWebsite
time to read 35 min | 6870 words

I thought about leaving the CRUD for Projects for the readers, but when I started to implement it myself, I realized that there were several things here that I hand't explained. As a quick note, I made some modifications to the code, to structure it better, there shouldn't be any changes to functionality, but I added a few utility functions and made some of the views more generalized.

In general, those changes are from having NoSuchUser and NoSuchProject views to having a single NoSuchObject view, nothing more. Okay, let's talk about projects. A project is a little bit more complicated than a user, since it actually contains a reference to a user. This means that we can't just use exact same techniques as we used for the CRUD operations on the User class. The changes are fairly minors, but they deserve their own post, so here it is, Views\Admin\EditProject.boo:

<?brail

      name = ""

      defaultAssigneeId = -1

      id = "0"

      if ?project:

         name = project.Name

         defaultAssigneeId = project.DefaultAssignee.Id

         id = project.Id.ToString()

      end

     

?>

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

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

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

<table>

      <tr>

            <td> 

                  ${HtmlHelper.LabelFor('project.Name','Project name:')}

            </td>

            <td>

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

            </td>

      <tr>

      <tr>

            <td>

      ${HtmlHelper.LabelFor('project.DefaultAssignee.Id','Default Assignee:')}

    </td>

    <td>

            ${HtmlHelper.Select('project.DefaultAssignee.Id')}

            ${HtmlHelper.CreateOptions(allUsers, 'Name','Id', defaultAssigneeId)}}

            ${HtmlHelper.EndSelect()}

    </td>

      </tr>

</table>

${HtmlHelper.SubmitButton('Save')}

${HtmlHelper.EndFieldSet()}

${HtmlHelper.EndForm()}

Most of what you see here we have already seen. The only new thing is the Select in the end, which would display the list of all the users that who can be the default assignee to this project. A couple of words on the CreateOptions() method. The first parameter is the collection of items to use, the second is the display text, the third is the value text. The forth is the id of the selected object. As always, this view is going to be used for both editing a project and adding a new one.

Another thing to note is the bolded line at the start of the view, where we use the '?' prefix to denote an optional variable. Where before we have to use IsDefined(), we can now just use the '?' prefix to get a null result when the value has not been defined for the view.

Now we can turn to write the SaveProject() method, which is a bit more complicated:

    public void SaveProject([ARDataBind("project", AutoLoadUnlessKeyIs = 0)]Project project,

        [ARFetch("project.DefaultAssignee.Id", Create=false, Required=true)] User defaultAssigneee)

    {

        RenderMessage( "Back to Projects", "Projects");

        try

        {

            if (project.DefaultAssignee == null)

                project.DefaultAssignee = defaultAssigneee;

            project.Save();

            Flash["good"] = string.Format(

                "Project {0} was saved successfully.", project.Name);

        }

        catch (Exception e)

        {

            Flash["DataBindErrors"] = project.ValidationErrorMessages;

            Flash["bad"] = e.Message;

            Flash["Debug"] = e.ToString();

        }

    }

As you can see, I'm using the ARDataBind for the project again, but now I combine it with ARFetch for the defaultAssignee. The reason for that is that while ARDataBind is smart enough to give me the correctly configured object when the object already exists in the database, it's not smart enough to figure out the correct connections by itself, hence, the ARFetch attribute. It simply gives me back the object with the same id as the one that was passed, if I see that the Default Assignee property on the project is null (which will only happen when we create a new project), I set the defaultAssignee manually. ARFetch will simply load a value from the database based on its primary key, so it's not as smart as ARDataBind, but as you can see, it certainly has it uses.

Here is the EditProject() and CreateProject() methods:

    public void CreateProject()

    {

        AddAllUsersToPropertyBag();

        RenderView("EditProject");

    }

 

    public void EditProject([ARFetch("id")]Project project)

    {

        AddAllUsersToPropertyBag();

        ValidateObjectToSendToView("project", "Projects", project);

    }

There isn't much going with them, CreateProject simply push all the users for the view to use, and render the EditProject view. EditProject uses ARFetch to get the correct project and make it (and all the users) available for the view to use. Here are the confirmProjectDelete and DeleteProject methods:

    public void ConfirmDeleteProject([ARFetch("id")]Project project)

    {

        if (project == null)

            RenderNoSuchObject("project");

        RenderDelete("deleteProject", "Projects", project);

    }

 

    public void DeleteProject([ARFetch("id")]Project project)

    {

        if (project == null)

        {

            RenderNoSuchObject("project");

            return;

        }

        project.Delete();

        RedirectToAction("Projects");

    }

You can probably guess that I'm very much in love with the ARFetch attribute :-) I trust that there isn't much need to explain what the code does, it's pretty straight forward.  Another change that I've made was with the Global.asax, where the code moved to a code behind file. I also added a session per request support, which will be helpful when we'll start dealing with lazy collections:

public class MythicalBugTrackerApplication : HttpApplication

{

    const string SessionScope = "Active.Record.SessionScope";

 

    public override void Init()

    {

        base.Init();

        log4net.Config.DOMConfigurator.Configure();

        Castle.ActiveRecord.Framework.IConfigurationSource source =

               System.Configuration.ConfigurationManager.GetSection("activerecord") as Castle.ActiveRecord.Framework.IConfigurationSource;

        Castle.ActiveRecord.ActiveRecordStarter.Initialize(typeof(MythicalBugTracker.Model.User).Assembly, source);

 

        this.BeginRequest += new EventHandler(MythicalBugTrackerApplication_BeginRequest);

        this.EndRequest += new EventHandler(MythicalBugTrackerApplication_EndRequest);

    }

 

    void MythicalBugTrackerApplication_EndRequest(object sender, EventArgs e)

    {

        ISessionScope scope = Context.Items[SessionScope] as ISessionScope;

        if (scope != null)

            scope.Dispose();

    }

 

    void MythicalBugTrackerApplication_BeginRequest(object sender, EventArgs e)

    {

        Context.Items.Add(SessionScope, new SessionScope());

    }

}

The utility methods I added are:

  • ValidateObjectToSendToView - make sure that the object is not null (if yes, redirect to the NoSuchObject page) and push it to the view
  • RenderNoSuchObject - display a generic error about missing object
  • RenderDelete - render the cofirm delete page
  • AddAllUsersToPropertyBag - make all the users avialable to the view
  • RenderMessage - render the generic message (good, bad, debug, databind violations)

Note: You can now get the source for the Mythical Bug Tracker here, In order to run this code you need:

  • New build of castle, revision 1700 and onward at least (just get the latest from Subversion, that should be enough).
  • .Net 2.0
  • SQL Server or SQL Express

Enjoy,

time to read 33 min | 6566 words

Before we start with our regular content, a couple of bug fixes, the ValidateLength on the Name and Email properties of the User class should looks like this: 

[

ValidateLength(0,50)]

Specifying just one number means the exact length of the string.

Okay, so far we have seen how to edit users and then save them to the database, but we haven't seen yet how to actually create the users. For myself, I think the users will be less than inclined to add themselves to the database by hand, using SQL, and I can just imagine the expression on the face of the DBA when he hears this suggestion.

Okay, I hear you, I won't bring that up again, looks like we have to do some work to make it happen. The question is, how much work is needed? We did a fair amount of work yesterday, to support editing users, how much of this work do you think we can use to support creating users?

Before anything else, I'm getting tired of having to remember all those URLs in my head, we'll add an index page to our administration section. Add an empty method called Index() to the AdminController class and create the following view at Views\Admin\Index.boo

<ul>

  <li>

    ${HtmlHelper.LinkTo('Admin Projects','projects')}

  </li>

  <li>

    ${HtmlHelper.LinkTo('Admin Users', 'users')}

  </li>

</ul>

Now we create the CreateUser method:

    public void CreateUser()

    {

        RenderView("EditUser");

    }

In the EditUser.boo view, we need to make a single change, and that is to initialize the id varaible to "0" instead of empty string, like this (fragement):

<?brail

      name = ""

      email = ""

      id = "0"

Let's beef up our SaveUser method, to make it capable of handling both existing and new users, the new stuff is bolded:

    public void SaveUser([ARDataBind("user",AutoLoadUnlessKeyIs=0)]User user)

    {

        RenderView("GenericMessage");

        try

        {

            user.Save();

            Flash["good"] = string.Format(

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

        }

        catch (Exception e)

        {

            Flash["DataBindErrors"] = user.ValidationErrorMessages;

            Flash["bad"] = e.Message;

        }

    }

I added some better handling for errors, and we now direct the framework to automatically load the user from the database unless the id is equal to 0. I think that this may require a couple of changes to the GenericMessage view, like this:

<p>

<font color="red">

<?brail

 if IsDefined('bad'):

  output bad

 end

?>

</font>

</p>

<?brail if IsDefined('DataBindErrors'): ?>

<table>

      <tr>

            <thead>Validation Errors</thead>

      </tr>

      <?brail for error in DataBindErrors: ?>

      <tr>

            <td>${error}</td>

      </tr>

      <?brail end ?>

</table>

<?brail end ?>

<p>

      <font color="green">

      <?brail

      if IsDefined('good'):

        output good

      end

      ?>

      </font>

</p>

Okay, we're done with creating users. Play with it a bit, try to add a non email adress, put it strings that are too long, use names that already eixst, etc. I won't claim that this is the greatest UI in the world, but it's functional.

Next stop, deleting users, should be simple. For now, we'll build it using normal pages & post backs. In a short while, I'll see how to use the Ajax support in MonoRail. We'll start by editing the users.boo page to add a link to the delete page, like this (fragement):

    <td>

      <?brail

        output HtmlHelper.LinkTo("Delete","admin","ConfirmDeleteUser",user.Id)

      ?>

    </td>

As long we're on a roll, we'll add a link to the create user page, like this (fragement):

${HtmlHelper.LinkTo('Create New User','admin','CreateUser')}

Now we get to write the delete functionality itself. We'll start with Views\Admin\ConfirmDeleteUser.boo, which looks like this:

<p>

  ${user.Name} says he is sorry, are you still angry?

</p>

<p>

      ${HtmlHelper.LinkTo('Yes, Delete!', 'Admin', 'DeleteUser',User.Id)} 

<p>

</p>

      ${HtmlHelper.LinkTo('Well, no...', 'Admin', 'Users')} 

</p>

And now to write the code that will actually handle all of this. Here it is in full:

    public void ConfirmDeleteUser([ARFetch("id",Create=false)]User user)

    {

        if (user == null)

        {

            RenderView("NoSuchUser");

            return;

        }

        PropertyBag.Add("User", user);

    }

 

    public void DeleteUser([ARFetch("id", Create = false)]User user)

    {

        if (user == null)

        {

            RenderView("NoSuchUser");

            return;

        }

        user.Delete();

        RedirectToAction("Users");

    }

This require some explanation. The ARFetch attribute will take an id and will pull it out of the database and into our parameter (passing null if it can't find it). We just make sure that it is handled properly. This is it, we know has full CRUD for a user. I'm going to leave the implementation of similar operation for projects as an exercise for the reader.

Next time, we'll see how we can add some Ajax stuff to it, so we can keep up with the hype.

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.

time to read 62 min | 12260 words

Okay, so far we played around with MonoRail, and I, at least, has quite a bit of fun. Just to point something out before we carry on: While MonoRail has strong scaffolding capabilities (the ability to generate pages for CRUD operations automatically), I don't like the idea of using them, we're going to build everything from scratch.

The first thing that we need to do is to add is some wrapper HTML so our pages will not be HTML fragements (like the previous one). We do it by specifying a layout. Layouts are very similar to master pages in ASP.Net 2.0. We will build a very simple one before we more on to more complex stuff. A layout usually resides in the Views\Layouts folder, since I aspire to be original and surprising, I decided to call mine Default.boo. (Bet you didn't see that coming). Here is the boring layout:

<!DOCTYPE xhtml

PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html>

<body>

${ChildOutput}

</body>

</html >

Notice the part where it says ${ChildOutput}? That is the part where the view data will go. Now what? Shall we start with administration? Why not. Here is what we are trying to achieve. We'll have a secured part of our site where an administrator can manage users and projects. We'll ignore security for now, (I'll show how to add that on later on), so let's start doing something that you can show around in the office.

As usual, we'll start by creating a class, this time, it's the AdminControll class. Right now it is going to have only two methods, Users() and Projects():

[Layout("Admin")]

public class AdminController : SmartDispatcherController

{

    public void Users()

    {

        User[] users = User.FindAll();

        PropertyBag.Add("allUsers", PaginationHelper.CreatePagination(users, 25));

    }

 

    public void Projects()

    {

        Project[] projects = Project.FindAll();

        PropertyBag.Add("allProjects", PaginationHelper.CreatePagination(projects, 25));

    }

}

I'm using SmartDispatcherController as the base class because I'll later want the functionality it provides (match request parameters to method overload). For the moment, we can ignore the PaginationHelper calls. We add the Admin.boo layout to Views\Layouts, this one just proclaims that this is the administrator section:

<!DOCTYPE xhtml

PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"

"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">

<html>

<title>Administrator Section</title>

<body>

${ChildOutput}

</body>

</html >

We create a new directory called Admin under Views, and create two files there, Users.boo and Projects.boo. Here is how the Users.boo view looks like. It's quite a bit of code, but there is nothing complicate to it:

<p>Our users:</p>

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

      <tr>

            <td>

                  <B>Id</B>

            </td>

            <td>

                  <B>Name</B>

            </td>

            <td>

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

      </tr>

<%

for user in allUsers:

%>

      <tr>

            <td>

                  ${user.Id}

            </td>

            <td>

                  ${user.Name}

            </td>

            <td>

                  ${user.Email}

            </td>

      </tr>

<%

end

%>

</table>

<p>Pagination Data:</p>

<div class="pagination">

<table width="70%" >

  <tr>

  <td>Showing Users: ${allUsers.FirstItem} - ${allUsers.LastItem} of ${allUsers.TotalItems}</td>

  <td>

<%

      if allUsers.HasFirst:

            output PaginationHelper.CreatePageLink( 1, "first" )

      else:

            output "first"

      end

%>

  </td>  <td>

<%

      if allUsers.HasPrevious:

            output PaginationHelper.CreatePageLink( allUsers.PreviousIndex, "prev" )

      else:

            output "prev"

      end

%>

 </td> <td>

     

<%    if allUsers.HasNext:

            output PaginationHelper.CreatePageLink( allUsers.NextIndex, "next" )

      else:

            output "next"

      end

%>

 </td>  <td>

<%

      if allUsers.HasLast:

            output PaginationHelper.CreatePageLink( allUsers.LastIndex, "last" )

      else:

            output "last"

      end

%>

  </td>

  </tr>

</table>

</div>

The code above should be very familiar for anyone who ever used a <Repeater> on ASP.Net. Most of the code here is simplly to deal with the pagination. We will remove this code in the near future and put it where it belongs, in a reusable method. But for now, it does the job. You get a nice list of users (well, one user, but you get the point). We will return to this page in the future, and see how pagination is handled automatically by the framework, without us needing to do anything about it.

The Projects.boo view is nearly identical, but there is a piece of logic here that we can push outside. Just like in the rest of our code, we want to avoid duplication. We will now seperate the code to a seperate re-usable function (can you hear the OO fanatics pant in excitement in the back of the room?)

Brail will make any methods or classes that are stored in Views\CommonScripts to all the views in the application. So we start by creating this directory and then the script: "Views\CommonScripts\PaginationFooter.boo". Common Scripts are different than views, since they don't have a default output and they can't mix markup and code like views can do. This require some minor changes to the pagination footer:

import System.Text

import Castle.MonoRail.Framework.Helpers

 

def PaginationFooter(page as Page, pagingHelper as PaginationHelper):

      output = StringBuilder()

      output.Append("""<p>Pagination Data:</p> <div class="pagination"> <table width="70%" >

           <tr>   <td>Showing: ${page.FirstItem} - ${page.LastItem} of ${page.TotalItems}</td>

             <td>""") 

      if page.HasFirst:

            output.Append( pagingHelper.CreatePageLink( 1, "first" ) )

      else:

            output.Append( "first" )

      end

      output.Append("  </td>  <td> ")

      if page.HasPrevious:

            output.Append( pagingHelper.CreatePageLink( page.PreviousIndex, "prev" ) )

      else:

            output.Append( "prev" )

      end

      output.Append(" </td> <td> ")

      if page.HasNext:

            output.Append( pagingHelper.CreatePageLink( page.NextIndex, "next" ) )

      else:

            output.Append( "next" )

      end

      output.Append(" </td>  <td> ")

      if page.HasLast:

            output.Append( pagingHelper.CreatePageLink( page.LastIndex, "last" ) )

      else:

            output.Append( "last" )

      end

      output.Append("""  </td>  </tr> </table>  </div>""")

      return output.ToString()

end

We needed to move from markup & code to explicit string building, which we return to the caller. Just to show you the difference, check out how th Project.boo page looks like now:

<p>Our Projects:</p>

 

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

      <tr>

            <td>

                  <B>Id</B>

            </td>

            <td>

                  <B>Name</B>

            </td>

            <td>

                  <B>Default Assignee</B></td>

      </tr>

<%

      for project in allProjects:%><tr>

            <td>

                  ${project.Id}

            </td>

            <td>

                  ${project.Name}

            </td>

            <td>

                  ${project.DefaultAssignee.Name}

            </td>

      </tr>

<%

      end

%>

</table>

${PaginationFooter(allProjects, PaginationHelper)}

I made similar modifications to the Users.boo view, cutting its length it by more than half. It's pretty late around here, so I think I'll finish with this for today.

Future topics:

  • Adding edit / create / update functionality for both users and projects.
  • Using Ajax to connect users to projects.
  • Adding security to the application.- this one will touch on more subjects than just MonoRail

In the meantime, have a good day, and don't let Mrs. WebForms catch you looking at prettier options :-)

time to read 20 min | 3958 words

Okay, so far we saw that MonoRail can be used to serve static files just as well as a 1993 Gopher Server. Considerring that Gopher is dead, we might want to add some stuff to our application. But allow me to give you a very short overview of the way MonoRail works.

MonoRail has Controllers and Views. A controller is a normal class that inherit from the Castle.MonoRail.Framework.Controller class. When a request comes in, MonoRail analyze the URL and dispatch the request to the proper controller method. This is important, unlike in ASP.Net web forms, where the smallest piece you handle was the Page, here the smallest piece is a controller's method. This has various implications on the way you structure you application, but we will touch those a little later.

Controllers are usually named [DescriptiveName]Controller, and any public method without parameters is an action. In the case of our previous example, the HomeController.Default action matched the /home/default.rails URL. Later we'll see how to use parameterized method and databinding to

Views are used to show information to the user, there is usually one to one match between a view and an action, although several actions can use the same view (or a single action can use several views). The file path of a view is significant, since this is used to match the action to the view. So the view for the HomeController.Default action is: "Views/Home/Default.boo".

Okay, that is enough theory for now, I think. Let's see what we can do with this information. Our goal now is to show the user a list of projects. Because currently the database is empty, we will manually populate it with dummy data, so we can see the results of our actions. Here is the script to create the data:

--Cleaning things that may have remained from our tests earlier

DELETE FROM UsersProjects

DELETE FROM Bugs

DELETE FROM Projects

DELETE FROM Users

 

DECLARE @UserID int

 

INSERT INTO Users

SELECT 'Dummy','Dummy@Foo.com','Unhashed password'

 

SELECT @UserId = SCOPE_IDENTITY()

 

INSERT INTO Projects

SELECT 'Default Project',@UserID

 

INSERT INTO Projects

SELECT 'Second Project',@UserID

Now we can have something real.

We'll start by adding the following references:

  • MythicalBugTracker.Model
  • Castle.ActiveRecord.dll
  • NHibernate.dll

Add the following the HomeController's Default method:

    public void Default()

    {

        Project[] projects = Project.FindAll();

        PropertyBag.Add("Projects", projects);

    }

Edit the Views\Home\Default.boo file so it will look like this:

<p>Projects:</p>

<ul>

<%

     for project in Projects:

           output "<li>${project.Name}</li>"

%>

</il>

We need to add the Active Record configuration to the web.config file. Add the following line to the <configSections> node:

<section name="activerecord"

     type="Castle.ActiveRecord.Framework.Config.ActiveRecordSectionHandler, Castle.ActiveRecord" />

Add the Active Record and Connection String information:

<connectionStrings>

     <add name="MythicalBugsTracker" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=C:\MythicalBugTracker\Data\MythicalBugTracker.mdf;Integrated Security=True;"/>

</connectionStrings>

 

<activerecord isWeb="true">

     <config>

           <add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver" />

           <add key="hibernate.dialect"                 value="NHibernate.Dialect.MsSql2000Dialect" />

           <add key="hibernate.connection.provider"     value="NHibernate.Connection.DriverConnectionProvider" />

           <add key="hibernate.connection.connection_string" value="ConnectionString = ${MythicalBugsTracker}" />

     </config>

</activerecord>

Important: Notice that we set the isWeb attribute to true. This is needed so Active Record would store its state properly. Failing to do these could cause random failures, especially under load.

We are almost set to go, now we just need to initalize Active Record, we do this by adding global.asax file and putting the initalization in the Application_Start event:

<%@ Application Language="C#" %>

 

<script runat="server">

   

    void Application_Start(object sender, EventArgs e)

    {

         Castle.ActiveRecord.Framework.IConfigurationSource source =

            System.Configuration.ConfigurationManager.GetSection("activerecord") as
                    Castle.ActiveRecord.Framework.IConfigurationSource;

            Castle.ActiveRecord.ActiveRecordStarter.Initialize(
                typeof(MythicalBugTracker.Model.User).Assembly, 
                source);

    }

</script>

Now we can go to http://localhost:8080/Home/default.rails again, and see what kind of monster we created. Here is the result:

Projects:

  • Default Project
  • Second Project

It's still not an enterprise ready web site, but we made some steps away from gopher fame.

time to read 15 min | 2903 words

So we finally made it to actually talk about MonoRail. :-) Just to set the expectations straight, we are going to create a web site for to the Mythical Bug Tracker application, using MonoRail and Brail. It's going be functional, but it is not going to look pretty. I'm not a designer, and I'm not going to waste my time talking about CSS and color coordination when you can go to someone who actually knows something about them and learn something useful.

In two sentences, what is MonoRail?

MonoRail is a Model - View - Controller framework for ASP.Net applications. It replaces the WebForms model, and simplify development by quite a bit. MonoRail encourages complete seperation of business logic and UI logic. To render the pages, MonoRail uses View Engines, one of those ViewEngines is called NVelocity, and it is a port of the Velocity project from Java. The second View Engine is called Brail and it was written by me, so I think you can guess what we're going to be working with now :-) You can go here for a more objective comparisson of the view engines.

Before we start, we need to create a new class library project called MythicalBugTracker.Web. Add the following references:

  • Castle.MonoRail.Framework.dll
  • Castle.MonoRail.Views.Brail.dll
  • Boo.Lang.dll
  • Boo.Lang.Compiler.dll
  • Boo.Lang.Parser.dll
  • Castle.Components.Common.EmailSender.dll
  • Castle.Components.Common.EmailSender.SmtpEmailSender.dll

Add a web.config file with the following information:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

  <configSections>

    <section name="monoRail"

             type ="Castle.MonoRail.Framework.Configuration.MonoRailSectionHandler, Castle.MonoRail.Framework"/>

  </configSections>

 

  <monoRail>

    <controllers>

      <assembly>MythicalBugTracker.Web</assembly>

    </controllers>

    <viewEngine viewPathRoot="Views"

                 customEngine="Castle.MonoRail.Views.Brail.BooViewEngine, Castle.MonoRail.Views.Brail"/>

  </monoRail>

 

  <system.web>

    <httpHandlers>

      <add verb="*" path="*.rails"

           type="Castle.MonoRail.Framework.MonoRailHttpHandlerFactory, Castle.MonoRail.Framework"/>

    </httpHandlers>

    <httpModules>

      <add name="monorail" type="Castle.MonoRail.Framework.EngineContextModule, Castle.MonoRail.Framework" />

    </httpModules>

  </system.web>

 

</configuration>

We define MonoRail, the controllers assembly and the view engine. We also mapped the ".rails" extention to MonoRail. I promise that it will all be much clearer in a couple of minutes, but basically we just tell the ASP.Net runtime to use MonoRail for processing request, and we configure MonoRail with the appropriate parameters.

Now we need to setup the project so we can execute it, this require a bit of tinkering with the project's settings:

  • Go to MythicalBugTracker.Web properties > Build and change the Output Path to "bin\" only.
  • Go to MythicalBugTracker.Web properties > Debug
  • Select "Start External Program" and put "C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\WebDev.WebServer.exe" in the text box.
  • In the "Command line arguments", put the this: "/port:8080" "/path:F:\MythicalBugTracker\MythicalBugTracker.Web"

This is just manually configuring the ASP.Net Development Server to run when we run out project, and making sure that the compiled output of the assembly is in the bin directory, where the ASP.Net Development Server expects it. One thing to note if you try to run this on IIS, make sure that you've mapped the ".rails" extention to the asp_net ISAPI filter, otherwise it will not work.

We're nearly done with the setup, now all we need is to create the following directories in our project:

  • Controllers
  • Views

It's a bit of a setup, but there is a MonoRail integration package for VS.Net that makes it all automatic. (I'm working with a newer build than the one the integration was built against, which is why I'm doing everything manually.).

Now we can start writing code, we'll start by create a HomeController class on the Controllers directory, here is the entire class defination:

public class HomeController : Controller

{

    public void Default()

    {

    }

}

Now, create a folder called "Home" in the Views folder, and create a file called "Default.boo" with the following text:

I thought I would get some real content here!

(And they tell me I'm not funny, Huh!)

Press Ctrl+F5 and point your browser to: http://localhost:8080/Home/default.rails

Okay, so we could do it more easily if we were using plain old HTML, but now we know that we have the environment setup correctly, we can move on to slightly more complicated things.

FUTURE POSTS

  1. Partial writes, IO_Uring and safety - about one day from now
  2. Configuration values & Escape hatches - 5 days from now
  3. What happens when a sparse file allocation fails? - 7 days from now
  4. NTFS has an emergency stash of disk space - 9 days from now
  5. Challenge: Giving file system developer ulcer - 12 days from now

And 4 more posts are pending...

There are posts all the way to Feb 17, 2025

RECENT SERIES

  1. Challenge (77):
    20 Jan 2025 - What does this code do?
  2. Answer (13):
    22 Jan 2025 - What does this code do?
  3. Production post-mortem (2):
    17 Jan 2025 - Inspecting ourselves to death
  4. Performance discovery (2):
    10 Jan 2025 - IOPS vs. IOPS
View all series

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats
}