I didn't think it was possible, but .NET surprised me yet again with a cool feature I never knew existed: The ObservableCollection. This became available in .NET 3.0.
In essence, an ObservableCollection is a collection with an event you can connect to. The event fires when the collection changes. As usual, working with the .NET classes is so ridiculously easy, it feels like cheating.
The following is small test program to illustrate how the ObservableCollection works. To start, create an ObservableCollection and then store it in the Session object so it will persist between page post backs. I also added the code to pull it out of Session state when there is a page post back:
public partial class _Default : System.Web.UI.Page{ public ObservableCollection<int> MyInts; // ---- Page_Load ------------------------------ protected void Page_Load(object sender, EventArgs e) { if (IsPostBack == false) { MyInts = new ObservableCollection<int>(); MyInts.CollectionChanged += CollectionChangedHandler; Session["MyInts"] = MyInts; // store for use between postbacks } else { MyInts = Session["MyInts"] as ObservableCollection<int>; } }
Here's the event handler I hooked up to the ObservableCollection, it writes status strings to a ListBox. Note: The event handler fires in a different thread than the IIS process thread.
// ---- CollectionChangedHandler ----------------------------------- // // Something changed in the Observable collection public void CollectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e) { // need to dig around to get the current page and control to write to: // (because this is in a separate thread) Page CurrentPage = System.Web.HttpContext.Current.Handler as Page; ListBox LB = CurrentPage.FindControl("ListBoxHistory") as ListBox; switch (e.Action) { case NotifyCollectionChangedAction.Add: LB.Items.Add("Add: " + e.NewItems[0]); break; case NotifyCollectionChangedAction.Remove: LB.Items.Add("Remove: " + e.OldItems[0]); break; case NotifyCollectionChangedAction.Reset: LB.Items.Add("Reset: "); break; default: LB.Items.Add(e.Action.ToString()); break; } }
Next, add some buttons and code to exercise the ObservableCollection: <br /> <asp:Button ID="ButtonAdd" runat="server" Text="Add" OnClick="ButtonAdd_Click" /> <asp:Button ID="ButtonRemove" runat="server" Text="Remove" OnClick="ButtonRemove_Click" /> <asp:Button ID="ButtonReset" runat="server" Text="Reset" OnClick="ButtonReset_Click" /> <asp:Button ID="ButtonList" runat="server" Text="List" OnClick="ButtonList_Click" /> <br /> <asp:TextBox ID="TextBoxInt" runat="server" Width="51px"></asp:TextBox> <br /> <asp:ListBox ID="ListBoxHistory" runat="server" Height="255px" Width="195px"> </asp:ListBox> // ---- Add Button -------------------------------------- protected void ButtonAdd_Click(object sender, EventArgs e) { int Temp; if (int.TryParse(TextBoxInt.Text, out Temp) == true) MyInts.Add(Temp); } // ---- Remove Button -------------------------------------- protected void ButtonRemove_Click(object sender, EventArgs e) { int Temp; if (int.TryParse(TextBoxInt.Text, out Temp) == true) MyInts.Remove(Temp); } // ---- Button Reset ----------------------------------- protected void ButtonReset_Click(object sender, EventArgs e) { MyInts.Clear(); } // ---- Button List -------------------------------------- protected void ButtonList_Click(object sender, EventArgs e) { ListBoxHistory.Items.Add("MyInts:"); foreach (int i in MyInts) { // a bit of tweaking to get the text to be indented ListItem LI = new ListItem(" " + i.ToString()); LI.Text = Server.HtmlDecode(LI.Text); ListBoxHistory.Items.Add(LI); } }
Here's what it looks like after entering some numbers and clicking some buttons:
An interesting note is that I had to use: System.Web.HttpContext.Current.Response to write to a control on the page. As mentioned earlier, this implies that the notification event is in a thread separate from the IIS thread.
Another interesting note: From the online help:
Any public static (Shared in Visual Basic) members of this type are thread safe. Any instance members are not guaranteed to be thread safe
What does that mean to Asp.Net developers?
If you are going to share an ObservableCollection among different sessions, you'd better make it a static object.
I hope someone finds this useful.
Steve Wellens