Since I took the day off to prepare for our family cruise to Mexico tomorrow, I ran into an extra hour today to do with as I please. Having wanted to play around with Atlas (now ASP.NET AJAX) and Castle ActiveRecord for a while, why not cram them both into my free hour today. So I did exactly that, and I still have 10 minutes to spare to write this blog entry - and that includes the downloading of the software, installation, and reading some of the docs. Although it has become painfully obvious to me that I need a new PC as I think I wasted a good 5 to 10 minutes waiting for my PC to do "stuff."
I was amazed at how dumb foundingly
easy both these technologies make it to throw together a featureful web app. First I created a database table:
CREATE TABLE GalleryUser (
GalleryUserId int IDENTITY(1, 1) PRIMARY KEY,
FullName varchar(50) NOT NULL,
Login varchar(16) NOT NULL,
CreatedDate smalldatetime NOT NULL DEFAULT GETDATE()
)
then I went about creating a GalleryUser class marked up with ActiveRecord attributes. The attributes mapped my class properties to the table I just created above.
using System;
using Castle.ActiveRecord;
namespace Sneal.Gallery.Core
{
[ActiveRecord("GalleryUser")]
public class GalleryUser : ActiveRecordBase
{
private int id;
private string fullName;
private string login;
private DateTime createdDate;
public GalleryUser() { }
[PrimaryKey(PrimaryKeyType.Native, "GalleryUserId")]
public int Id
{
get { return id; }
set { id = value; }
}
[Property("FullName")]
public string FullName
{
get { return fullName; }
set { fullName = value; }
}
[Property("Login")]
public string Login
{
get { return login; }
set { login = value; }
}
[Property("CreatedDate")]
public DateTime CreatedDate
{
get { return createdDate; }
set { createdDate = value; }
}
public static GalleryUser[] FindAll()
{
return (GalleryUser[])FindAll(typeof(GalleryUser));
}
public static GalleryUser Find(int id)
{
return (GalleryUser)FindByPrimaryKey(typeof(GalleryUser), id);
}
}
}
Next I created a Global.asax to handle my ActiveRecord initialization and NHibernate session per request schtuff. This particular part (including my web.config mods) is where I spent the bulk of my time with ActiveRecord.
using System;
using System.Web;
using System.Web.Security;
using System.Configuration;
using Castle.ActiveRecord;
using Castle.ActiveRecord.Framework;
using Sneal.Gallery.Core;
namespace Sneal.Gallery.WebUI
{
public class Global : System.Web.HttpApplication
{
public Global()
{
this.BeginRequest += new EventHandler(Global_BeginRequest);
this.EndRequest += new EventHandler(Global_EndRequest);
}
protected void Application_Start(Object sender, EventArgs e)
{
IConfigurationSource source = ConfigurationSettings.GetConfig("activerecord") as IConfigurationSource;
ActiveRecordStarter.Initialize(source, typeof(GalleryUser));
}
public void Global_BeginRequest(object sender, EventArgs e)
{
HttpContext.Current.Items.Add("nh.sessionscope", new SessionScope());
}
public void Global_EndRequest(object sender, EventArgs e)
{
try
{
SessionScope scope = HttpContext.Current.Items["nh.sessionscope"] as SessionScope;
if (scope != null)
{
scope.Dispose();
}
}
catch (Exception ex)
{
HttpContext.Current.Trace.Warn("Error", "EndRequest: " + ex.Message, ex);
}
}
}
}
Now for the fun ASP.NET AJAX hookup in my ASPX page. This is where all of the AJAX magic happens, specially in the Update Panel. I could have thrown all of the controls into the UpdatePanel and then I wouldn't have needed to add the explicit AsyncPostBackTrigger reference, but hey - I wanted to see how it works. Having used Telerik's AJAX framework, which is quite good by they way, I found the ASP.NET AJAX framework even easier. It's so well integrated now into ASP.NET and Visual Studio, I can't imagine anyone not using it going forward. It won't be cool to use AJAX next year, it will just be the de facto standard.
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="Default.aspx.cs" Inherits="_Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>ASP.NET AJAX Test Page</title>
</head>
<body>
<form id="form1" runat="server">
<asp:ScriptManager ID="ScriptManager1" runat="server" />
<asp:UpdatePanel ID="UpdatePanel1" runat="server">
<ContentTemplate>
<asp:ListBox ID="lstUsers" runat="server" />
</ContentTemplate>
<Triggers>
<asp:AsyncPostBackTrigger ControlID="btnGetUsers" />
</Triggers>
</asp:UpdatePanel>
<asp:Button ID="btnGetUsers" runat="server" Text="Get users from DB"
OnClick="btnGetUsers_Click" />
<asp:TextBox ID="txtInfo" runat="server" EnableViewState="false" />
</form>
</body>
</html>
And the associated code behind, which looks just like it would without the AJAX magic. The fun part to note here is the data fetching; just too easy! So easy in fact I just skipped following any decent application patterns like MVP, but hey its just a tech spike.
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using Sneal.Gallery.Core;
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if (IsPostBack)
txtInfo.Text = "IsPostback = true";
else if (IsCallback) // doesn't work as hoped.
txtInfo.Text = "IsCallback = true";
else
txtInfo.Text = "Initial page load = true";
}
protected void btnGetUsers_Click(object sender, EventArgs e)
{
GalleryUser[] users = GalleryUser.FindAll();
foreach (GalleryUser user in users)
{
lstUsers.Items.Add(user.FullName);
}
}
}
In the end, I'm very happy with the results. And to my surprise my spike app worked on the first run.
My only issue is that I'm not sure I like ActiveRecord and its static data access methods on the actual objects. It seems like it's crossing the boundaries of separation of concern, but then again it didn't cost me any coding time... I'll have to think about it.