.NET & Funky Fresh

Syndication

News

  • <script type="text/javascript" src="http://ws.amazon.com/widgets/q?ServiceVersion=20070822&amp;MarketPlace=US&amp;ID=V20070822/US/bluspiconinc-20/8001/8b68bf4b-6724-40e7-99a5-a6decf6d8648"> </script>
Caliburn.Micro Soup to Nuts Pt. 4 – Working with Windows Phone 7

Hopefully, previous articles have you up to speed on what Caliburn.Micro is, its basic configuration, and how to take advantage of a few of its features. In this part, I want to talk about some WP7 specifics issues. It’s unfortunate that I have to call out WP7, but your going to find that while you may be an experienced WPF or Silverlight developer, that doesn’t make WP7 development a snap. Microsoft still has a long ways to go in making “three screens and the cloud” a reality. The new features in Caliburn.Micro are specifically designed to address some of the shortcomings in WP7, particularly around Navigation (with ViewModels and Screen Activation), Tombstoning and Launchers/Choosers.

 

Bootstrapper

Let’s start by getting our application configured correctly. In previous parts we began by creating a bootstrapper and adding it to our Application.Resources. We do the same thing on WP7, but we inherit our bootstrapper from the PhoneBootstrapper class. Here’s how the bootstrapper for our WP7 sample application looks:

using System;
using System.Collections.Generic;
using Microsoft.Phone.Tasks;

public class HelloWP7Bootstrapper : PhoneBootstrapper
{
    PhoneContainer container;

    protected override void Configure()
    {
        container = new PhoneContainer();

        container.RegisterSingleton(typeof(MainPageViewModel), "MainPageViewModel", typeof(MainPageViewModel));
        container.RegisterSingleton(typeof(PageTwoViewModel), "PageTwoViewModel", typeof(PageTwoViewModel));
        container.RegisterPerRequest(typeof(TabViewModel), null, typeof(TabViewModel));

        container.RegisterInstance(typeof(INavigationService), null, new FrameAdapter(RootFrame));
        container.RegisterInstance(typeof(IPhoneService), null, new PhoneApplicationServiceAdapter(PhoneService));

        container.Activator.InstallChooser<PhoneNumberChooserTask, PhoneNumberResult>();
        container.Activator.InstallLauncher<EmailComposeTask>();
    }

    protected override object GetInstance(Type service, string key)
    {
        return container.GetInstance(service, key);
    }

    protected override IEnumerable<object> GetAllInstances(Type service)
    {
        return container.GetAllInstances(service);
    }

    protected override void BuildUp(object instance)
    {
        container.BuildUp(instance);
    }
}

For WP7, there are less IoC container options available. In this case, I’ve written a simple container myself. You can get the full code for SimpleContainer in the Recipes section of the CM Documentation. Additionally, I inherited from SimpleContainer to create PhoneContainer, which just adds some custom activation logic related to Launchers/Choosers. We’ll dig into that a bit later. The most important thing to note in this code is the use of PhoneBootstrapper and the registrations of two services: INavigationService, which wraps the RootFrame and IPhoneService, which wraps the native PhoneService. The RootFrame and PhoneService are created by the PhoneBootstrapper and added to the appropriate places, so you can keep your App.xaml and App.xaml.cs files clean. Here’s what they should look like:

<Application x:Class="Caliburn.Micro.HelloWP7.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:Caliburn.Micro.HelloWP7">
    <Application.Resources>
        <local:HelloWP7Bootstrapper x:Key="bootstrapper" />
    </Application.Resources>
</Application>
using System.Windows;

public partial class App : Application
{
    public App()
    {
        InitializeComponent();
    }
}

 

INavigationService

Let’s dig into what the FrameAdapter implementation of INavigationService does for you. First, you should know that WP7 enforces a View-First approach to UI at the platform level. Like it or not (I don’t) the platform is going to create pages at will and the Frame control is going to conduct your application thusly. You don’t get to control that and there are no extensibility points, unlike the Silverlight version of the navigation framework. Rather than fight this, I’m going to recommend embracing the View-First approach for Pages in WP7, but maintaining a Model-First composition strategy for the sub-components of those pages. In order to support the View-First approach, I’ve enabled the FrameAdapter to hook into the native navigation frame’s functionality and augment it with the following behaviors:

When Navigating To a Page

  1. Use the new ViewModelLocator to conventionally determine the name of the VM that should be attached to the page being navigated to. Pull that VM by key out of the container.
  2. If a VM is found, use the ViewModelBinder to connect the Page to the located ViewModel.
  3. Examine the Page’s QueryString. Look for properties on the VM that match the QueryString parameters and inject them, performing the necessary type coercion.
  4. If the ViewModel implements the IActivate interface, call its Activate method.

When Navigating Away From a Page

  1. Detect whether the associated ViewModel implements the IGuardClose interface.
  2. If IGuardClose is implemented and the app is not being tombstoned or closed, invoke the CanClose method and use its result to optionally cancel page navigation.*
  3. If the ViewModel can close and implements the IDeactivate interface, call it’s Deactivate method.
  4. If the application is being tombstoned or closed, pass “true” to the Deactivate method indicating that the ViewModel should permanently close.

The behavior of the navigation service allows the correct VM to be hooked up to the page, allows that VM to be notified that it is being navigated to (IActivate), allows it to prevent navigation away from the current page (IGuardClose) and allows it to clean up after itself on navigation away, tombstoning or normal “closing” of the application (IDeactivate). All these interfaces (and a couple more) are implemented by the Screen class. I haven’t discussed Screens and Conductors yet, but you can get started taking advantage of them with just the simple information I have provided. If you prefer not to inherit from Screen, you can implement any of the interfaces individually of coarse. They provide a nice View-Model-Centric, testable and predictable way of responding to navigation without needing to wire up a ton of event handlers or write important application flow logic in the page’s code-behind. Simply follow the same naming conventions for View/ViewModels as normal, plug the FrameAdapter into your IoC container and implement whatever interfaces you care about on your VMs.

 

IPhoneService

There’s really not much to say about this. I abstracted an interface for the built-in PhoneApplicationService and created a decorator. This makes it easy for VMs to take a dependency on the functionality without being coupled to the actual phone service. I didn’t add any additional behavior.

 

Now that we have the basic service explanations over with, let’s create some Views and ViewModels to get our navigation working and to demonstrate a few other features. The default WP7 template creates a MainPage.xaml for you and configures the application to use that as its default starting point. We’ll use that, but change the Xaml to this:

<phone:PhoneApplicationPage x:Class="Caliburn.Micro.HelloWP7.MainPage"
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
                            xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
                            SupportedOrientations="Portrait"
                            Orientation="Portrait"
                            shell:SystemTray.IsVisible="True">
    <Grid Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <StackPanel Grid.Row="0"
                    Margin="24,24,0,12">
            <TextBlock Text="WP7 Caliburn.Micro"
                       Style='{StaticResource PhoneTextNormalStyle}' />
            <TextBlock Text='Main Page'
                       Margin='-3,-8,0,0'
                       Style='{StaticResource PhoneTextTitle1Style}' />
        </StackPanel>

        <Grid Grid.Row='1'>
            <Button x:Name='GotoPageTwo'
                    Content='Goto Page Two' />
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

The only noteworthy thing I have done here is to add a single Button to the page in order to demonstrate navigation. Here’s what the MainPageViewModel looks like:

using System;

public class MainPageViewModel
{
    readonly INavigationService navigationService;

    public MainPageViewModel(INavigationService navigationService)
    {
        this.navigationService = navigationService;
    }

    public void GotoPageTwo()
    {
        navigationService.Navigate(new Uri("/PageTwo.xaml?NumberOfTabs=5", UriKind.RelativeOrAbsolute));
    }
}

Here’s what it would look like if we could run it at this point:

PageOne

It’s pretty simple, but there are a few things worth mentioning. The first is that our INavigationService is being injected through the constructor. This will happen when we navigate to the page and the VM is conventionally resolved and wired up. In this case we haven’t implemented any interfaces or inherited from any base classes. We don’t really care about lifecycle. Take a look at the GotoPageTwo method (conventionally wired to the big button). We are using the INavigationService to tell the phone to go to PageTwo.xaml. Remember the query string and have a look at the ViewModel for PageTwo:

using System;
using System.Collections.Generic;
using System.Linq;

[SurviveTombstone]
public class PageTwoViewModel : Conductor<IScreen>.Collection.OneActive
{
    readonly Func<TabViewModel> createTab;

    public PageTwoViewModel(Func<TabViewModel> createTab)
    {
        this.createTab = createTab;
    }

    public int NumberOfTabs { get; set; }

    protected override void OnInitialize()
    {
        Enumerable.Range(1, NumberOfTabs).Apply(x =>{
            var tab = createTab();
            tab.DisplayName = "Item " + x;
            Items.Add(tab);
        });

        ActivateItem(Items[0]);
    }
}

The value of the the QueryString parameter “NumberOfTabs” is going to be injected into the property of the same name on the ViewModel. The OnInitialize method is part of the Activation lifecycle I mentioned above. If you notice in the bootstrapper’s configuration, PageTwoViewModel is a Singleton. OnInitialize will be called the first time this page is navigated to, not on any subsequent times, while OnActivate will be called every time this page is navigated to. These are properties of the Screen class from which this ViewModel ultimately inherits. Basically, all this method does is create a parameterized number of child ViewModels (TabViewModel) and then “Activates” the first one. The Items collection and Activation capabilities come from the Conductor base class. Just to remind you, I’m going to cover Screens and Conductors much more thoroughly in a future article. Also, take note of the [SurviveTombstone] attribute. We’ll get to that soon. First, here’s the view that this is bound to:

<phone:PhoneApplicationPage x:Class="Caliburn.Micro.HelloWP7.PageTwo"
                            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                            xmlns:phone="clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone"
                            xmlns:shell="clr-namespace:Microsoft.Phone.Shell;assembly=Microsoft.Phone"
                            SupportedOrientations="Portrait"
                            Orientation="Portrait"
                            shell:SystemTray.IsVisible="True">
    <Grid Background="Transparent">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>

        <StackPanel Grid.Row="0"
                    Margin="24,24,0,12">
            <TextBlock Text="WP7 Caliburn.Micro"
                       Style='{StaticResource PhoneTextNormalStyle}' />
            <TextBlock Text='Page Two'
                       Margin='-3,-8,0,0'
                       Style='{StaticResource PhoneTextTitle1Style}' />
        </StackPanel>

        <Grid Grid.Row='1'>
            <Grid.RowDefinitions>
                <RowDefinition Height='Auto' />
                <RowDefinition Height='*' />
            </Grid.RowDefinitions>

            <ListBox x:Name='Items'>
                <ListBox.ItemTemplate>
                    <DataTemplate>
                        <TextBlock Text='{Binding DisplayName}'
                                   Margin='2 0 12 0' 
                                   FontSize='30' />
                    </DataTemplate>
                </ListBox.ItemTemplate>
                <ListBox.ItemsPanel>
                    <ItemsPanelTemplate>
                        <StackPanel Orientation='Horizontal' />
                    </ItemsPanelTemplate>
                </ListBox.ItemsPanel>
            </ListBox>

            <ContentControl x:Name='ActiveItem' 
                            HorizontalContentAlignment='Stretch'
                            VerticalContentAlignment='Stretch'
                            Grid.Row='1' />
        </Grid>
    </Grid>
</phone:PhoneApplicationPage>

It looks like this when running:

PageTwo

This view is a bit more complex, but you only need concern yourself with the ListBox and the ContentControl. Here, we are using these two controls to create a Tabbed UI. The ListBox plays the part of the TabHeaders. It’s name causes it to automatically have its ItemsSource bound to the Items collection on the VM and its SelectedItem bound to the ActiveItem on the VM (these properties come from Conductor base class). The ContentControl has its Content property automatically bound to the ActiveItem property on the View and it is done using the View.Model attached property I discussed in the previous article. This allows the ActiveItem to be conventionally wired up to it’s view, enabling the display of the “Tab Content”. And yes, I am going to cover conventions in more depth in a future article as well. But, I want you to get a taste of how conventions, composition and conductors all play together. What we’ve just created is an MDI interface inside this particular page completely driven by the ViewModel. Let’s take a look at that TabViewModel and it’s corresponding View. First the VM:

using System;
using System.Windows;
using Microsoft.Phone.Tasks;

[SurviveTombstone]
public class TabViewModel : Screen, ILaunchChooser<PhoneNumberResult>
{
    string text;

    [SurviveTombstone]
    public string Text
    {
        get { return text; }
        set
        {
            text = value;
            NotifyOfPropertyChange(() => Text);
        }
    }

    public void Choose()
    {
        TaskLaunchRequested(this, TaskLaunchEventArgs.For<PhoneNumberChooserTask>());
    }

    public event EventHandler<TaskLaunchEventArgs> TaskLaunchRequested = delegate { };

    public void Handle(PhoneNumberResult message)
    {
        MessageBox.Show("The result was " + message.TaskResult, DisplayName, MessageBoxButton.OK); //You should abstract this…
    }
}

Now you should be noticing some interesting tidbits here. I’ll be coming back to those in a minute. Let’s take a look at the last piece in our application’s UI, the view for these tabs:

<UserControl x:Class="Caliburn.Micro.HelloWP7.TabView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel Background="{StaticResource PhoneChromeBrush}">
        <TextBlock x:Name="DisplayName"
                   Margin="8 0 0 0"
                   FontFamily="{StaticResource PhoneFontFamilyNormal}"
                   FontSize="{StaticResource PhoneFontSizeNormal}"
                   Foreground="{StaticResource PhoneForegroundBrush}" />
        <TextBox x:Name="Text" />
        <Button Content="Choose"
                x:Name="Choose" />
    </StackPanel>
</UserControl>

Here we just have a TextBox bound to our Text property and a Button that will execute our Choose method. Hopefully, you can see how all that comes together in the image above. Let’s add one more thing, just to make our application complete before we dive into the really interesting parts. I mentioned that I had modified my SimpleContainer to create a custom PhoneContainer. All I did was plug in a special Caliburn.Micro piece that helps us use Launchers/Choosers easier (discussed shortly). Pretty much every IoC Container has a place to plug in custom “activation” logic. This is code that gets a chance to execute whenever the container creates an instance of a class. For my simple container, I provided an overridable method to plug this in. It’s a bit different for each container, but it’s a powerful extensibility point. By plugging in CMs InstanceActivator class, we will make launchers/chooser so much easier for you. For now, I’ll show you what the PhoneContainer looks like. We’ll get to the launcher/chooser bit a little later:

using System;

public class PhoneContainer : SimpleContainer
{
    public PhoneContainer()
    {
        Activator = new InstanceActivator(type => GetInstance(type, null));
    }

    public InstanceActivator Activator { get; private set; }

    protected override object ActivateInstance(Type type, object[] args)
    {
        return Activator.ActivateInstance(base.ActivateInstance(type, args));
    }
}

 

Tombstoning

Now that we have all the pieces, I can explain why I’ve gone through the trouble of setting all this up: Tombstoning. At any point in time a WP7 application has to expect that it will be shutdown by the OS. For example, if someone is using your application and their friend calls them, the phone will Tombstone your application in order to process the call. When the call is over, your application will be Resurrected. A well designed WP7 application should restore itself to the exact state it was in before Tombstoning. The phone’s OS remembers the page you were on and Navigates to it automatically (read more here). Our Navigation system described above ensures that it gets hooked up with the right ViewModel. But, you must ensure that that View/ViewModel is put back in the same state it was in before Tombstoning. That’s where the [SurviveTombstone] attribute you saw comes into play. Whenever the OS attempts to Tombstone your application, Caliburn.Micro will grab the current page, extract its DataContext and see if it has any attributes that implement ITombstone. This interface is used to serialize a class or property by flattening it and storing it into the PhoneApplicationService.State property, which is persisted by the OS solely for the purpose of Resurrection (if it occurs). The [SurviveTombstone] attribute is a simple out-of-the-box implementation of this interface that understands how to walk an object graph, examine its properties and persist them. It has special knowledge of Conductors as well, so that it will persist their ActiveItem correctly and inspect their children. This is an opt-in mechanism. You must decorate every class as well as the properties you want persisted. If you look at the classes above, you can see that the PageTwoViewModel is attributed. Because it is a Conductor, the index of its ActiveItem will be persisted and each of its Items will be traversed as well. Because each TabViewModel is attributed, it will be inspected for persistable properties. The Text property is attributed, so that property on each child item will be persisted. This mechanism is customizable and entirely replaceable if you don’t like it. First, you can create your own attributes that implement ITombstone to customize all or part of the process. Second, you can override the SelectInstancesToTombstone and SelectInstancesToResurrect methods on the PhoneBootstrapper to determine what should be persisted to begin with. As I mentioned, by default, we only inspect the DataContext of the current page. But, you could grab several key VMs from your IoC and supply them to the persistence mechanism here if you like. Lastly, you can override the Tombstone and Resurrect methods on the PhoneBootstrapper to replace the built-in mechanism all together. If you download the the sample attached to this article and run the application, you will see that this all works as expected. Try navigating to the second page, selecting a tab other than the first one and typing some text into the text box. Next, click the “Choose” button (we are going to talk about choosers next). After the chooser launches, press the phone’s back button to cancel. Immediately press F5 in Visual Studio to re-attach the debugger (remember that that chooser caused your application to be tombstoned). After the debugger is attached, your application goes back to page two with the proper tab selected and the text filled back in the text box. The chooser result is displayed as well.** Here’s what you should see:

PageTwoPostChooser

 

Launchers and Choosers

One unique aspect to WP7 development is Tasks. They come in two flavors. Tasks that simply launch a built-in phone application without returning data, such as EmailComposeTask, are called Launchers. Tasks that return data back to your application, such as PhoneNumberChooserTask, are called Choosers. Now, let me tell you how I really feel…the WP7 API for Choosers is a software design abomination. You need to know how it works for your own benefit and to better appreciate what I am going to show you, so let me take a moment to explain. Executing a task is as simple as creating the class and calling its Show method. But, remember that Tombstoning thing? As soon as you call Show, your application gets Tombstoned. So, right now you should be wondering, “But what if I am using a Chooser? How do I get the results back into my application?” Well, the official guidance is that you should have created that Chooser in the constructor of your code-behind file and wired up its completed event. So, when the phone infrastructure tries to resurrect your application, it will navigate to that page, the chooser will be re-created and the event re-wired.  When you re-wire to the event, your event handler is called immediately. The infrastructure is trying to fake a cross-app-instance callback. Now, does that sound like it’s going to play nice with MVVM…or any attempt at decent software design? One thing is for sure, you have to be very careful about when you create the Chooser and wire the event. For example, lets say you had the idea that you would create these Choosers as singletons at startup and just wire their events then and there, in order to remove that from your pages. Well, it won’t work. You wired the event too early. It’s also quite possible to wire it too late. It must be done at just the right time. But, what is the right time? From what I can tell, it’s not clearly defined by their API. If you do it in your page’s constructor, it should work though. But should you handle the event in the constructor? Probably not, especially since your main UI isn’t even visible yet. So, you probably at least need to wire for the Loaded event and delay handling of the callback until the UI is visible. As you can see, this part of the WP7 API is a disaster. I’ve done some things to try to improve this and make it more friendly to UI architecture. The following functionality is handled by the InstanceActivator and must be hooked into your IoC container for it to be enabled. Here’s how it works:

  • First, developers register the Launcher/Chooser types they intend to use with the Activator. If you look back at the sample’s bootstrapper, you will see that I used the methods InstallLauncher and InstallChooser.
  • Each instance that is created by the IoC container will be inspected by the Activator to see if it implements certain interfaces.
  • If the instance implements ILaunchTask or ILaunchChooser<TResult>, the activator will wire itself to the event defined by the interface.
  • When the VM wants to execute a task, it raises ILaunchTask.TaskLaunchRequested. You can see an example of this in the the TabViewModel.Choose method.
  • The infrastructure is notified of the event and then determines if the sender implements IConfigureTask<TTask>. If it does, the task instance is passed to the sender’s Configure method. (Some tasks have parameters which must be set, but the sample above does not.)
  • Next, the infrastructure “records” the sender of the event so that it knows who to call back after resurrection.
  • The infrastructure now calls Show on the task and your application is Tombstoned.
  • When, your application resurrects, it should put itself back in the state it was before tombstoning.
  • Since all instances come from the IoC container, we inspect each instance that is created to see if it was the one we “recorded” as the sender of the task launch.
  • When we find a match, we attempt to call ILaunchChooser<TResult>.Handle with the Chooser result at the “latest possible time.” The latest possible time is evaluated based on the features of the class. If it is IViewAware, we call handle after the View’s Loaded event fires. If it isn’t but implements IActivate, we call Handle after the class is activated (assuming it isn’t already activated.) If these conditions aren’t met, we call Handle immediately.

It sounds complicated. That’s mostly because, frankly, Choosers are not very friendly to any sort of reasonable software design. However, I think the result of the custom Activator, especially when combined with the [SurviveTombstone] attribute is a very natural developer experience. If you run the sample, you will see that this does indeed work and that the Chooser result is passed back to the correct VM after it’s View is shown.

 

I’ve gone through a lot of WP7 specific stuff in this article. Please remember that all of this is completely optional. You can still leverage Caliburn.Micro without it. I think the FrameAdapter will prove to be the most useful and generalizable feature for building UI. Hopefully the simple tombstoning mechanism and Launcher/Chooser abstraction will help you too.

 

*Even though the IGuardClose interface was designed to handle async scenarios, you must use it synchronously in WP7. This is due to a flaw in the design of Silverlight Navigation Framework.

 

 

**Sometimes the first time I do this, the debugger does not re-attach correctly and the application never resurrects at all. If this happens to you, just try it again ;)


Posted 08-07-2010 10:42 PM by Rob Eisenberg

[Advertisement]

Comments

Matt Hidinger wrote re: Caliburn.Micro Soup to Nuts Pt. 4 – Working with Windows Phone 7
on 08-08-2010 6:03 PM

Rob, I think a lot of the design enhamcents you've made here look really solid. Specifically I love the SurviveTombstone idea, and the Launcher/Chooser improvements. I will be branching one of my current apps and see how well they behave, and of course drop any feedback on the CM discussion tab.

Thanks again for the efforts and this very descriptive post.

-Matt

Bas ter Vrugt wrote re: Caliburn.Micro Soup to Nuts Pt. 4 – Working with Windows Phone 7
on 08-09-2010 4:06 AM

Great article!

Pretty funny, IPhoneService on a WM7 device.

Rob Eisenberg wrote re: Caliburn.Micro Soup to Nuts Pt. 4 – Working with Windows Phone 7
on 08-09-2010 10:08 AM

LOL I didn't even see that...even the first few times I read your comment.

.NET & Funky Fresh wrote Caliburn.Micro Soup to Nuts Part 5 – IResult and Coroutines
on 08-21-2010 2:35 PM

Before our WP7 detour , we were deep in the thick of Actions . I mentioned that there was one more compelling

Christopher Bennage wrote re: Caliburn.Micro Soup to Nuts Pt. 4 – Working with Windows Phone 7
on 08-30-2010 5:11 PM

N.B. If you forget to clean out the App.xaml.cs, you'll run into problems. Specifically, SetMethodBinding() on ActionMessage won't find the method and you'll receive the "No target found for method" exception.

Oleksandr Krakovetskiy blog wrote Дайджест технических материалов #5 (Windows Phone 7)
on 10-27-2010 7:33 AM

Tools, Books, Guides Windows Phone 7 Developer Tools RTM (online installer) , ISO UI Design and Interaction

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)