Tuesday, August 26, 2008

ASP.NET MVC Preview 4

Gotta say, I'm impressed. I've been tinkering with it for a short while, and already I'm happy. It's a complex beast, that's for sure, but there's a lot of convention (rather than configuration) that manages a lot of the complexity for you.

That said, there is little in the way of official guidance out there so far. Rob Conery's MVC Storefront screencast series has been a great resource for me, but sometimes I find it hard to follow. Meanwhile a lot of the really great blog posts are getting out of date as the framework matures.

Maturity is a good thing, though, even if it means the information we have on the framework is spotty. There are a few good books on the subject coming out soon, but that doesn't help me now. My employer is encouraging me to get acquainted with the framework, as we'll be using it (likely before the official release) to replace our customer-facing website. Which is exciting - I'm optimistic about the project - but also worrying, especially if there are large breaking changes between Preview 4 and release.

But alas. All this in the name of progress - and don't let my worries fool you, it is progress, away from the leaky abstractions and inane page lifecycle of Web Forms. It is not a step back as some profess. Certainly it is easy to use ASP.NET MVC to create code that is hard to understand - but the same is true of any technology, no matter the paradigm.

In short, I'm absolutely in love with the framework, and although I have my worries, I have no doubt I'll continue to use it in preference to Web Forms for all my projects moving forward.

Monday, August 25, 2008

Delegate powered collection?

So, I'm playing around with some LINQ to SQL ideas. One of my ideas centers around the idea of trying to abstract one of my many-to-many relationships away from callers into the datacontext. To do this, I've created what I call a Delegate Powered Collection (better names are welcome).

It's really very simple - in place of method implementations, the ICollection members simply call a delegate passed in during the collection's initialization. The delegates are all Func and Action delegates, and so accept llambda functions as arguments. I haven't written a constructor (yet?) but I imagine it'd be pretty messy to do so.

Anyway, here's the code:

    1 public class DelegatePoweredCollection<T> : ICollection<T> {

    2     public Action<T> AddFunction { get; set; }

    3     public Action ClearFunction { get; set; }

    4     public Func<T, bool> ContainsFunction { get; set; }

    5     public Action<T[], int> CopyToFunction { get; set; }

    6     public Func<int> CountFunction { get; set; }

    7     public Func<bool> IsReadOnlyFunction { get; set; }

    8     public Func<T, bool> RemoveFunction { get; set; }

    9     public Func<IEnumerator<T>> GetEnumeratorFunction { get; set; }

   10 

   11     #region ICollection<T> Members

   12 

   13     public void Add(T item) {

   14         if (AddFunction == null)

   15             throw new NotImplementedException();

   16         AddFunction.Invoke(item);

   17     }

   18 

   19     public void Clear() {

   20         if (ClearFunction == null)

   21             throw new NotImplementedException();

   22         ClearFunction.Invoke();

   23     }

   24 

   25     public bool Contains(T item) {

   26         if (ContainsFunction == null)

   27             throw new NotImplementedException();

   28         return ContainsFunction.Invoke(item);

   29     }

   30 

   31     public void CopyTo(T[] array, int arrayIndex) {

   32         if (CopyToFunction == null)

   33             throw new NotImplementedException();

   34         CopyToFunction.Invoke(array, arrayIndex);

   35     }

   36 

   37     public int Count {

   38         get {

   39             if (CountFunction == null)

   40                 throw new NotImplementedException();

   41             return CountFunction.Invoke();

   42         }

   43     }

   44 

   45     public bool IsReadOnly {

   46         get {

   47             if (IsReadOnlyFunction == null)

   48                 throw new NotImplementedException();

   49             return IsReadOnlyFunction.Invoke();

   50         }

   51     }

   52 

   53     public bool Remove(T item) {

   54         if (RemoveFunction == null)

   55             throw new NotImplementedException();

   56         return RemoveFunction.Invoke(item);

   57     }

   58 

   59     #endregion

   60 

   61     #region IEnumerable<T> Members

   62 

   63     public IEnumerator<T> GetEnumerator() {

   64         if (GetEnumeratorFunction == null)

   65             throw new NotImplementedException();

   66         return GetEnumeratorFunction.Invoke();

   67     }

   68 

   69     #endregion

   70 

   71     #region IEnumerable Members

   72 

   73     IEnumerator IEnumerable.GetEnumerator() {

   74         return GetEnumerator();

   75     }

   76 

   77     #endregion

   78 }

As you can see, this implementation just passes off execution to the delegates passed in as properties. If the delegate for a particular method is null, then it throws a NotImplementedException.



Here's how I use it:

    1 public partial class Account {

    2     private DelegatePoweredCollection<Group> _groups;

    3 

    4     private void SetupGroupsList() {

    5         _groups = new DelegatePoweredCollection<Group>();

    6         _groups.AddFunction = g => AddToGroup(g);

    7         _groups.ClearFunction = () => RemoveFromAllGroups();

    8         _groups.ContainsFunction = g => UserIsInGroup(g);

    9         _groups.CountFunction = () => this.AccountToGroupMaps

   10             .Count(map => map.Account == this);

   11         _groups.GetEnumeratorFunction = () => GetAllGroupsForAccount();

   12         _groups.IsReadOnlyFunction = () => false;

   13         _groups.RemoveFunction = g => RemoveFromGroup(g);

   14     }

   15 

   16     /// <summary>

   17     /// Gets an ICollection of Groups that this account belongs to

   18     /// </summary>

   19     public ICollection<Group> Groups {

   20         get {

   21             if (_groups == null) SetupGroupsList();

   22             return _groups;

   23         }

   24     }

   25 

   26     private void AddToGroup(Group group) {

   27         // Check to see if a group map already exists.

   28         if (this.AccountToGroupMaps.Any(

   29             map => map.Group == group && map.Account == this)

   30         ) {

   31             // We don't need to add it again.

   32             return;

   33         }

   34 

   35         // Create new group map between this account and

   36         // the specified group

   37         AccountToGroupMap newMap = new AccountToGroupMap();

   38         newMap.Group = group;

   39         newMap.Account = this;

   40 

   41         // Add the mapping to both the group and the account.

   42         this.AccountToGroupMaps.Add(newMap);

   43         group.AccountToGroupMaps.Add(newMap);

   44     }

   45 

   46     private bool RemoveFromGroup(Group group) {

   47         // Remove the group map between this account and the

   48         // specified group

   49         AccountToGroupMap map = this.AccountToGroupMaps

   50             .SingleOrDefault(m => m.Group == group);

   51 

   52         // If the mapping doesn't exist, the user isn't in the

   53         // group to begin with - just return.

   54         if (map == null) return false;

   55 

   56         // Remove the mapping from both the account and the group.

   57         this.AccountToGroupMaps.Remove(map);

   58         group.AccountToGroupMaps.Remove(map);

   59 

   60         return true;

   61     }

   62 

   63     private void RemoveFromAllGroups() {

   64         var maps = this.AccountToGroupMaps

   65             .Where(map => map.Account == this);

   66         foreach (var m in maps) {

   67             this.AccountToGroupMaps.Remove(m);

   68         }

   69     }

   70 

   71     private bool UserIsInGroup(Group g) {

   72         return this.AccountToGroupMaps

   73             .Any(map => map.Account == this && map.Group == g);

   74     }

   75 

   76     private IEnumerator<Group> GetAllGroupsForAccount() {

   77         foreach (var group in this.AccountToGroupMaps

   78             .Where(map => map.Account == this)

   79             .Select(map => map.Group)) {

   80             yield return group;

   81         }

   82     }

   83 }

This particular implementation hides the mapping table in between my m:n association, allowing calling code to treat the Groups collection like any other collection, with the actual mapping being done behind the scenes.

I looked around for a bit, but couldn't find any similar implementations - did I just not do enough research? Have I invented a wheel better made elsewhere? Any comments? Let me know what you think. =)

As for performance - I haven't clocked it or tested it at all, but I'm willing to bet it's easily vastly slower than a 'real' collection implementation - but I think in this case the tradeoff between exposing that mapping object outside of the datacontext is worth a little bit lower performance, especially in this case - this isn't a mission critical section of the application.

Friday, August 22, 2008

Coding in the Shadows?

Well I wanted to use 'Coding in the dark' but apparently that's taken somewhere... *chuckle*

Ah well. The whole idea is that I'm sitting here, thinking it'll be a neat idea to follow the lead many of my peers have taken and start a programming blog. It's 12:20am, and dark - hence the shadows. Plus I figure it sounds mysterious...

I'm a dork.

Anyway - I'm Erik, and I am a C# programmer (admitting you have a disease is the first step to a cure). I've been programming for a long time, mostly as a hobby, but have within the past couple years taken it on professionally. Most recently I've taken on a C# development job full-time, and it's been a blast so far. =)

This blog is going to be for all the posts my girlfriend won't let me put on MySpace. I've started sipping the TDD / DDD Kool-ade, and I'd like to inflict my ramblings on the rest of the world. Ne'er you mind the fifty-million other programming blogs out here - this one is "special!"

I'm a newbie, despite my years of coding which I will henceforth refer to as 'learning the framework' - thus, my disclaimer: Neither I, nor my employer(s), are responsible for anything posted here. A great deal of what you are about to see is going to be drivel, I'm sure, so if watching people work laborously through the process of trial and error (and error, and error, and error) gets your blood going, then by all means read on. =)