Oren Eini

CEO of RavenDB

a NoSQL Open Source Document Database

Get in touch with me:

[email protected] +972 52-548-6969

Posts: 7,527
|
Comments: 51,163
Privacy Policy · Terms
filter by tags archive
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 3 min | 452 words

Another note about typing. In my opinion, people are often confused about the meaning of static typing vs. dynamic typing and explicit typing vs. implicit typing. The two are not neccearily connected. In general, a programming langague will fall into one of this categories:

  • Staticly typed, explicit typing - Java, C++, C#, VB.Net, etc
  • Staticly typed, implicit typing - Boo, C# 3.0, VB.Net 9.0 etc
  • Dynamically typed, implicit typing - Javascript, Ruby, Smalltalk, Brail :-), etc
  • Dynamically typed, explicit typing - Can't think of a single language with this, nor a single reason to want this.

Boo, C# 3.0, VB.Net 9.0 are smart enough to infer what the type of a variable is, without the developer having to tell the compiler (over and over again) what the type of the variable is.

Staticaly typed languages are very good, in my opinion, but explicit typing is a chore, nothing more. After I saw how easy it is to work with Boo, I just can't believe how hard I have to work in other languages just to make the compiler work.

Dynamic langauges are cool (Boo has some of the features that they can offer, and they are simply amazing), but you the ability to check the program for syntax validity. This is not a trivial matter. Yes, syntax violations are easy to fix, but if I write a bunch of code, and I need to fix the errors there one after the other, instead of one bunch, it's annoying and wasting my time. I'm not talking about unit testing here, I'm talking before I get to the point where my program even run.

In short, I think that the sweat spot for me is Boo. Staticly typed, implicit typing (but can be explicit if you want), with dynamic features avialable if you want them. I see the point in dynamic languages, and I enjoy doing work with Javascript (the only dynamic langauge I work with on regular basis), doing things that I could never do in C#.

But the point is that 90% of what you can do in a dynamic langauge you can do in a static one if the compiler is smart enough. The best is if the compiler allows you to say "yes, I know what I'm doing, let's do this bit on runtime".

time to read 2 min | 220 words

I'm reading James Robertson's blog, mainly for the rants. I mostly agree with what he says, if not with the tone, but today I read something of his in the comments that really annoyed me.

Detection of a missing method is utterly irrelevant. Why? Because such a flaw would turn up in initial testing.

I just did a bunch of javascript, which is a dynamic language with just this kind of behavior. Sorry, this isn't even close to satisfying. I had about.. oh... maybe 50 lines of code, and I kept running into problems with method not found, etc.

If I don't have an IDE when I program (and I do significant work from Notepad2), I like to compile and see what went wrong. I usually don't get those method names correctly. I misspell, or mis capitalize, or forget to put a closing bracket or something.

A compile gives me all those errors in a format that I can see and fix immediately. Runtime errors means that I have to fix those erorrs one by one. This isn't fun.

time to read 1 min | 109 words

Okay, now you know why I try to avoid releases of Rhino Mocks, every time I make a release, I usually have a week of sudden activity, with new features / fixes coming in.

This time it's a regression that happened with regard to generic types. The issue is that you can't mock any generic type more than once. I had tests for mocking generic types, but I didn't have any tests for trying the same type multiply times.

Well, now I do, and it's fixed. So you can go ang get sources and binaries from this page.

time to read 13 min | 2499 words

Stefan asks What Is Wrong With Javascript, I think that I've at least a partial answer to that. Here are my reasons:

  • Until very recently, Javascript was just too slow by far to consider writing applications with it.
  • Lack of a rich class library.
  • Until recently, lack of good debugging tools.
  • The browser is a really sucky place to develop in.
  • The impression of being a Toy Languague.
  • Lack of strong typing, this bytes me so often that I feel like screaming. I missed a semi column, or misspelled a varaible... go figure when/where this is going to blow (usually silently).
  • The browser is not a nice place to work with.
  • Easily modificable by the users.
  • Easily disabled by the users.
  • While the langauge is mostly standartize, there were (and are) problems with the environments that you work with, which makes life painful.
  • No typing is annoying to me.
  • Did I mention that the browser is not an application platform?

Maybe I'm bitter, but I'm butting my head against the wall in a relatively simple JavaScript problem for a couple of days now, and this is not a new feeling. Everytime that I need to do something with Javascript I approach it with disdain, not because the langauge is bad, I actually enjoy a lot of the things that I can do with Javascript. I don't like it because I know that I'm going to spend quite a few hours chasing stupid stuff all over the place.

For instance, take the most recent puzzle, creating input controls dynamically and sending them to the server:

<script language="javascript" type="text/javascript">

function addUser()

{

    var users = document.getElementById('users');

    var hidden = document.createElement('input');

    hidden.value = 'foo';

    users.appendChild(hidden);

    var i =0;

    for(var node = users.firstChild;node !=null; node = node.nextSibling)

    {

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

                continue;

            node.id = 'project.Users['+i+'].Id';

        i++;

    }

}

</script>

And the HTML:

<form id="form1" runat="server">

    <div id="users">

        <input id="Button1" type="button" value="Add User" onclick="javascript:addUser();" />

    </div>

    <asp:TextBox ID="PostBackValues" runat="server" Height="225px" TextMode="MultiLine" Width="596px"></asp:TextBox><br />

    <asp:Button ID="submit" runat="server" Text="submit" OnClick="submit_Click" /><br />

</form>

Now, pressing the add users cause a nice text input to be added to the page, I then hit sumbit, and watch the result from this code-behind:

protected void submit_Click(object sender, EventArgs e)

{

    foreach (string key in Request.Params.AllKeys)

    {

        PostBackValues.Text += string.Format(" {0}: {1}\r\n", key, Request.Params[key]);

    }

}

I don't see the values I just put in. Even more important, I can't see them in the request stream when I'm using Fiddler to check this. What happened to them, I don't know, and all my debugging turned out blank. I know that there is a two line fix for this, and probably a reasonable explanation, but getting to this point wasn't a fun process, and hitting a wall like that isn't uncommon for me when using Javascript. I had similar problems doing some smart UI with tables & colors in Javascript, especially since I wanted to do this on both IE and Firefox.

time to read 1 min | 63 words

Okay, this release should be a major improvement memory wise, I changed the implementation so instead of generating dynamic assemblies per MockRepository, all the Mock Repositories will generate to the same assembly.

This should prove to be a dramatic reduction in the amount of space that Rhino Mocks consumes.  Sources and binaries are avialable here.

time to read 1 min | 141 words


I just watched DNR TV #5, and I must say that it was one of the most amazing videos that I have ever seen. This is especially true when you consider that the feature being implemented on the show was one I wished that I had nearly a year ago.
This video features Mark Miller doing on-the-fly implementation of source code organizing (sort by visibility, then by member type, then alphebatically, etc).
Beside being a cool demonstration, and one of the few videos that managed to make me stop everything and watch them, the features of Code Rush that were shown were simply amazing. I've already downloaded the trail version, and I'm using it right now. Simply awesome show, and awesome technology.
The ease of developing with the DXCore is amazing, as well.

time to read 4 min | 711 words

Okay, a couple of months ago I posted about Finding Use for SqlClr for date processing, since it was much easier to do it in .Net than in SQL. Since then I retracted my statement about it.

If you read my blog for a while, you probably noticed that I'm doing quite a bit of performance tuning lately. After making the switch from SqlClr to T-Sql, I noticed a sudden drop in performance. Some investigation lead me to the change from SqlClr function to T-Sql functon.

Here is the source code for the .Net function (yes, I occasionally do VB.Net):

<SqlFunction()> _

Public Shared Function FirstDayOfMonth(ByVal year As Integer, ByVal month As Integer) As DateTime
   
Return New DateTime(year, month, 1)
End Function

And here is the T-Sql function:

CREATE

FUNCTION FirstDayOfMonth(@year int, @month int)
RETURNS DATETIME WITH SCHEMABINDING AS
BEGIN
   
RETURN DATEADD(MONTH,@month-1,DATEADD(YEAR,@year-1900,0))
END

As you can see, none of them do anything terribly complicated. I then run the following query on a table with 40 million records:

SELECT

1 FROM BigTable
WHERE SqlClr.FirstDayOfMonth(YEAR(Date),MONTH(Date)) = '20010101'

This query has no meaning whatsoever, it just make sure that the function is running for a long time. This query run for 34 seconds, before returning (an empty result set).

SELECT

1 FROM BigTable
WHERE [T-Sql].FirstDayOfMonth(YEAR(Date),MONTH(Date)) = '20010101'

This query run for 3 minutes and 29 seconds, 7 times slower than SqlClr! I'm not sure how to take this news, since I was completely convinced that it would be the other side that would win the race. Anyone got an explanation?


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,

FUTURE POSTS

No future posts left, oh my!

RECENT SERIES

  1. RavenDB Cloud (2):
    26 Nov 2024 - Auto scaling
  2. Challenge (75):
    01 Jul 2024 - Efficient snapshotable state
  3. Recording (14):
    19 Jun 2024 - Building a Database Engine in C# & .NET
  4. re (33):
    28 May 2024 - Secure Drop protocol
  5. Meta Blog (2):
    23 Jan 2024 - I'm a JS Developer now
View all series

Syndication

Main feed Feed Stats
Comments feed   Comments Feed Stats
}