Remote Outlook Alerts Add In

I do a lot of work from my workstation using Remote Desktop (or RDP). Because I only have one monitor, I usually view the remote screen in full screen mode, and so I have to switch back to my workstation screen where Outlook runs to see any new email or meeting/appointment alerts. And, like most developers, I can get absorbed in my work and forget what time it is! So I needed a solution that would alert me whenever a meeting reminder popped up on my workstation desktop or if I had new emails. I found this article at the Channel 9 site from a couple of years ago that seemed to do what I needed with an Outlook Add In, but so much has changed in the Office Object Model, that it would not even compile. However there was some good logic that I could borrow for an updated version of the Add In.

So, I fired up Visual Studio 2010 and began a new Outlook Add In project called RemoteOutlookAlerts. It set up the base project structure and files for me.

image


1) The generated Add In class


2) The class used to invoke the command-line MSG command

3) The configuration form

The important parts as shown above are: 1) the generated class file, ThisAddIn.cs; 2) the NetMessage class I modified from the original article (it used NetSend) to send the alerts across the wire using the Msg utility; and, 3) the SettingsForm I created to allow me to configure where to send the alerts, how many emails to ‘log’ before sending an alert, and whether or not to enable alerts to be sent (I’m not always working in RDP).

The Add In class generated by the project template wizard has two important methods: thisAddInStartup and thisAddInShutdown. This is where you build your Add In menu, set up any event handlers you will need, and in my case, set up a polling timer to check whether or not enough new emails have arrived to send an alert to the remote desktop. For the Add In menu, I followed the example from the original article:

private void buildMenu()
{
    _menuBar = this.Application.ActiveExplorer().CommandBars.ActiveMenuBar;
    _helpMenuIndex = _menuBar.Controls[MenuBefore].Index;
    _topMenu = (Office.CommandBarPopup)(
_menuBar.Controls.Add(Office.MsoControlType.msoControlPopup,
Type.Missing, Type.Missing, _helpMenuIndex, true)); _topMenu.Caption = "Remote Alerts"; _topMenu.Visible = true; _settingsMenu = (Office.CommandBarButton)(
_topMenu.Controls.Add(Office.MsoControlType.msoControlButton,
Type.Missing, Type.Missing, Type.Missing, true)); _settingsMenu.Caption = "Remote Alert Notification Settings..."; _settingsMenu.Visible = true; _settingsMenu.Enabled = true; }

My modified startup code looks like this:

private void thisAddInStartup(object sender, System.EventArgs e)
{
    buildMenu();

    Application.Reminder += thisAddInReminder;
    Application.NewMail += thisAddInNewMail;
    _settingsMenu.Click += _settingsMenu_Click;

    // 1 minute default for polling timer
    emailBucketTimer = new System.Timers.Timer { Interval = 60000 };
    emailBucketTimer.Elapsed += emailCollectionTimerElapsed;
    emailBucketTimer.Enabled = true;
}

I set up separate event handlers for new Reminders and for new Emails, since email notifications occur with each email that arrives, and I didn’t want to have one message box appear on my remote desktop for each new email. I took the approach of having the NewMail event handler increment a count, using a polling timer to check the current count, and if the count becomes equal to or exceeds the configured count, send out the alert.

I also turned off the timer in the Shutdown event handler.

The new Reminder event handler is almost identical to the one used in the original article, so I won’t spend any time on it. The NewMail event handler simply increments a private variable, and when the timer fires, that count is compared to the configured count like so:

void thisAddInNewMail()
{
    _emailBucketCount += 1;
}

private void emailCollectionTimerElapsed(object sender, ElapsedEventArgs e)
{
    if (Settings.Default["RemoteRemindersEnabled"].ToString() == "True")
    {
        if (_emailBucketCount > 
Convert.ToInt32(Settings.Default["RemoteRemindersNumber"])) { string emailMsg = String.Format("You've Got Mail!"); NetMessage.Send(Settings.Default["RemoteRemindersAddress"].ToString(), Settings.Default["RemoteRemindersUser"].ToString(),
emailMsg); _emailBucketCount = 0; } } }

The Send method of the NetMessage class is based on invoking the Msg command just like you would from a command prompt. There was one interesting twist I had to solve, and that is that Msg is not always located in the Windows\System32 directory. In 64-bit Windows, it is located elsewhere. So I used some hand waving and trickeration to establish the path to the Msg utility on any system. My solution is included in the source code you can grab at the end of the article. The rest of the logic is fairly straightforward.

var myArguments = String.Format("/Server:{0} {1} {2}", address, user, message);
var systemDir = Is64BitOperatingSystem ? "\\Sysnative\\" : "\\System32\\";
var p = new Process
{
    StartInfo =
    {
         FileName = Environment.GetEnvironmentVariable("WINDIR") + systemDir + "MSG",
         Arguments = myArguments,
         CreateNoWindow = true,
         UseShellExecute = false
    }
};
p.Start();

I also created and used the built-in Settings facility that generates app.config entries for the configurable settings. These are read and saved when the form is opened and closed from the Outlook Add In menu. It has four app settings entries:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <sectionGroup name="userSettings" 
type="System.Configuration.UserSettingsGroup, System,
Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089" > <section name="RemoteOutlookAlerts.Properties.Settings"
type="System.Configuration.ClientSettingsSection,
System, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089"
allowExeDefinition="MachineToLocalUser"
requirePermission="false" /> </sectionGroup> </configSections> <userSettings> <RemoteOutlookAlerts.Properties.Settings> <setting name="RemoteRemindersAddress" serializeAs="String"> <value /> </setting> <setting name="RemoteRemindersUser" serializeAs="String"> <value /> </setting> <setting name="RemoteRemindersNumber" serializeAs="String"> <value>5</value> </setting> <setting name="RemoteRemindersEnabled" serializeAs="String"> <value>False</value> </setting> </RemoteOutlookAlerts.Properties.Settings> </userSettings> </configuration>

I strong-named the assembly using sn –k RemoteOutlookAlerts.snk from the project directory, and signed the assembly with my Authenticode Certificate.

I created a setup project to install the Add In and grabbed a messages icon for the Programs and Features Control Panel applet. Here is a link to an MSDN article covering the current best practices for deploying Office Add Ins. I signed the msi as well.

Finally, there is a small security tweak you’re most likely going to have to set up on the target computer to avoid getting Error 5 (Unable to Create Session) messages.

On the target machine, open up the Registry Editor and navigate to
HKLM\SYSTEM\CurrentControlSet\Control\Terminal Server
Then change the following value:

Name : AllowRemoteRPC
Type : REG_DWORD
Value : 1

Reboot the target machine.

After you’ve installed the Add In, open up Outlook, select the Add Ins menu and then the Remote Alerts item. This is the configuration menu (with a nice Watermarked TextBox extension method to boot):

image

Since the remote machine I’m using is on my own network and it’s DHCP-enabled, I enter the IP address of the machine, my username, and then adjust the number of emails and check the checkbox if I’m going to start a session. This is what it looks like on the Remote Desktop when a Reminder alert appears:

image

And this is what it looks like after x number of new emails have arrived in my Inbox:

image

If you grab the source, and have any suggestions for improvement, please don’t hesitate to contact me. Enjoy, and I hope this helps!

UPDATE: There is one bug I am still working to solve. I followed the best practices from the MSDN article above, but if you delete the bin folder of the main project after building the setup project, the installation does not seem to know about the installed location of the .vsto and .manifest files. Instead, when you start up Outlook, it complains about the RemoteOutlookAlerts.vsto it cannot find. So for now, you can right-click the Setup project and select Install right from Visual Studio 2010, and, as long as you do not delete the Release build binaries, the Add In will work just fine. I will update this post again when I find a solution that allows posting the installer here.

Source Code in a zip archive

Bob Baker

Technorati Tags: ,

Running LightSwitch 2-tier applications on Windows 8

I wanted to show a LightSwitch application running on Windows 8 at our last Orlando .Net Users Group meeting, but I didn’t get a chance to work out the kinks that Windows 8 presented in time. It being Saturday today, and the good football games not starting until right about now (3:30 PM EDT, or GMT-5), I took some time this afternoon and worked it out.

It’s rather simple actually. I had published the application from my Windows 7 workstation, copied the Publish folder over to the Windows 8 installation that lives on a bootable .vhd on my laptop (this is the approach I used), and followed the instructions in the install.htm file that’s included. I’m actually fibbing about completely following the instructions. Since I had not attempted to install LightSwitch on Windows 8, I skipped the last step.

I ran the database creation script from an elevated command prompt as instructed. To get to an elevated command prompt, you have to right-click the Command Prompt placed on your Windows 8 Metro Start Screen by Visual Studio 11 Developer Edition, and select Run As Administrator by clicking on the Advanced charm in the App Bar that appears (or do it manually if you did not install the full Visual Studio 11). I verified that the web.config in the Application Files folder was set up correctly. But I did NOT run the command line using the Security Admin tool that comes with LightSwitch  (Microsoft.LightSwitch.SecurityAdmin.exe). Of course, the application timed out connecting to the database.

Being a bit of a hacker, I wondered (not out loud mind you) whether or not I could just copy the Tools folder where the LightSwitch Security Admin tool lives over to my Windows 8 installation and run it. Guess what? I could, you can, it runs, and my LightSwitch application started right up. I set up the application with Windows Authentication; if you use Forms authentication, you will need to supply a password. I had to put my full name and the path to the web.config in quotes.

Hope this helps. I’m sure the LightSwitch team is already working on making deployment onto Windows 8 and the new release of SQL Server Express much easier.

Bob Baker

Technorati Tags: ,,

Filter a collection using the Specification Pattern

I had never thought to use the Specification pattern as the basis for filtering a collection of data objects in search results. Call me late to the party, I guess. At any rate, today’s post is published so that I’ll have easy access to this code as well as my notes, and you, dear reader, can share in my forgetfulness.

The Specification pattern is part of the original Gang of Four (GoF) patterns. The entry on Wikipedia says:

In computer programming, the specification pattern is a particular software design pattern, whereby business logic can be recombined by chaining the business logic together using Boolean logic.

I decided to try to use this pattern as the basis for some search result filters against a collection returned from a database or web service. I will illustrate one such filter here.

First, we’ll define an interface:

using System;
using System.Linq.Expressions;

namespace SpecificationApp.Common.Specification
{
    public interface ISpecification<T>
    {
        Expression<Func<T, bool>> Predicate { get; }
        bool IsSatisfiedBy(T entity);
    }
}
Then, we implement the interface, and add some operator overloads:
using System;
using System.Linq;
using System.Linq.Expressions;

namespace SpecificationApp.Common.Specification
{
    public class Specification<T> : ISpecification<T>
    {
        private readonly Expression<Func<T, bool>> _predicate;

        public Specification(Expression<Func<T, bool>> predicate)
        {
            _predicate = predicate;
        }

        public Expression<Func<T, bool>> Predicate
        {
            get { return _predicate; }
        }

        public bool IsSatisfiedBy(T entity)
        {
            return _predicate.Compile().Invoke(entity);
        }

        public static Specification<T> operator &(Specification<T> leftSide, Specification<T> rightSide)
        {
            var rightInvoke = Expression.Invoke(rightSide.Predicate, leftSide.Predicate.Parameters.Cast<Expression>());
            var newExpression = Expression.MakeBinary(ExpressionType.AndAlso, leftSide.Predicate.Body, rightInvoke);
            return new Specification<T>(
                Expression.Lambda<Func<T, bool>>(newExpression, leftSide.Predicate.Parameters)
              );
        }

        public static Specification<T> operator |(Specification<T> leftSide, Specification<T> rightSide)
        {
            var rightInvoke = Expression.Invoke(rightSide.Predicate, leftSide.Predicate.Parameters.Cast<Expression>());
            var newExpression = Expression.MakeBinary(ExpressionType.OrElse, leftSide.Predicate.Body, rightInvoke);
            return new Specification<T>(
                Expression.Lambda<Func<T, bool>>(newExpression, leftSide.Predicate.Parameters)
              );
        }
    }
}

Then, assuming we will be using a collection of Person, we implement the PersonSpecifications class:

using SpecificationApp.Common.BusinessEntities;
using SpecificationApp.Common.Specification;

namespace SpecificationApp.Common.Specifications
{
    public class PersonSpecifications
    {
        public static Specification<Person> HasSSN
        {
            get
            {
                return new Specification<Person>(p => p.SocialSecurityNumber != null 
&& p.SocialSecurityNumber != 0); } } } }

Finally, we add usage of these specifications  as filters to our Person class:

using SpecificationApp.Common.Specifications;

namespace SpecificationApp.Common.BusinessEntities
{
    public class Person : BaseEntity
    {
        // entity properties - Id and other common properties are in BaseEntity      
        public int ? SocialSecurityNumber { get; set; }
        public string FirstName { get; set; }
        public string MiddleName { get; set; }
        public string LastName { get; set; }

// specifications based filter public bool HasSSN { get { return (PersonSpecifications.HasSSN).IsSatisfiedBy(this); } } } }

Now we can reference any of our filter specifications in as simple an example as a web page code-behind (or in a WPF or Silverlight ViewModel with property references instead of direct UI binding) like so:

var people = GetPeople();
if (ckSSNFilter.Checked) // a checkbox
{
    GridView1.DataSource = people.Where(i => i.HasSSN);
}
else
{
     GridView1.DataSource = people;
}
GridView1.DataBind();

Off course, because we’re using the Specification pattern and Linq, we can stack specifications to your heart’s content. This really comes in handy when you have some complex filtering to do. We did a log of leg work to get here, but the top-level code we’ll be working with the most is simple and fluent.

I put together a simple sample console application illustrating the above here. Hope it helps.

Bob Baker

Technorati Tags: ,,

Orlando Code Camp 2011 Phone Wallpapers

If you’re an iPhone, Android, or Windows Phone 7 owner (or any phone with an 800x400 display for that matter), and you are planning to attend the Orlando Code Camp in March, here’s an opportunity to show your ONETUG pride by using one of three wallpapers on your phone that I put together earlier this evening. Here they are:

CC2011Bkg2 CC2011Bkg3 CC2011Bkg4

I make no claim to the graphic arts, but wanted something for my own Samsung Focus. Once I shared them with some of fellow Code Camp developers, it was a no brainer to offer them up. You can download a zip file containing the three wallpapers here. What I did was email the graphics to an email account that I receive on the phone and then save them to my phone. If your display is slightly smaller, most phones let you drag the area you want to include. Since these are for my Windows Phone 7 phone, I have placed a dark band at the top for the status icons, which you’ll want to include in the wallpaper. Enjoy!

Bob Baker

WCF RIA Services 1.0 with ASP.Net 4.0 WebForms – some CRUD

I was trying to put together a demo to show the use of a shared WCF RIA Services Class Library with both a Silverlight client and an ASP.Net client. However, the only examples and documentation I could find about the ASP.Net side were essentially rehashes of the original RIA walk-through, in which a DomainDataSource control is dropped on a page, and then a GridView control is bound to it. This is nice for quick presentations, but what if you wanted to render something more useful like, say, a data entry form with live inserts and updates (and even deletes)?

This is what we’re after:

  ASP.Net: Silverlight:

image

image

Both platforms need to be able to accept an existing person to edit their registration, as well as insert a new registrant. I was able to find bits and pieces of the solution from about 5 sources, and so I wrote this post so that the information and solution will now all be in one place. Let’s dive into some code. Here’s what the project layout looks like:

image

CodeCamp2011.RIA.Date – the WCF RIA Services class library; there are plenty of resources out there to help you build one of these effectively, but I found the original MSDN walkthrough and Bryan Noyes series very helpful.
CodeCamp2011.RIA.UI – the Silverlight client – I used the JetPack Navigation project template (link can be found here).
CodeCamp2011.RIA.UI.Web – the ASP.Net client and host for the Silverlight client (the site also hosts the RIA Authentication bits).

I’m not going to spend much time in the Silverlight side except to say that I use the MVVM pattern (with the excellent Caliburn.Micro framework) for all of my current WPF, Silverlight and WP7 projects. For the Silverlight Save command in the Registration ViewModel, I simply used a basic RIA code approach:

public void Save()
{
    if (!string.IsNullOrEmpty(Password1) && !string.IsNullOrEmpty(Password2) && Password1 == Password2)
        Person.PasswordHash = Encryption.ComputePasswordHash(Password1);

    if (IsNewMode)
        context.Persons.Add(Person);
    else
    {
        if (Person.EntityState == EntityState.Detached) {context.Persons.Attach(Person);}
    }
    try
    {
        context.SubmitChanges();
        this.MessageBox.ShowMessage("Registration Saved", "Welcome to CodeCamp 2011!");       
    }
    catch(DomainOperationException ex)
    {
        this.MessageBox.ShowException(ex);
    }
    mode = Mode.Edit;
    Password1 = string.Empty;
    Password2 = string.Empty;
    IsDirty = false;
}

Person is one of my Entity classes, context is an instance of my RIA DomainContext and MessageBox is an instance of a StandardMessageBox class I wrote that implements an Interface and that shows a parameterized MessageBox. I did this so that the ViewModel is testable (via a separate constructor) without UI dependencies. For this demo, I did not use MEF to allow constructor injection of this interface type, and leave that as an exercise.

On the ASP.Net side, things appeared to be a bit iffy. We do not have direct access to the DomainService Context that we enjoy in Silverlight clients, but instead only have access to the DomainService instance or instances we have built. So that I would have one DomainService, I built separate DomainService class files for the various entities in my EF4 POCO model, marked them all as partial, and removed the [EnableClientAccess()] attribute from all but one of them (thanks, Colin Blair). The class library also has the EDMX Model in the its Web project so that I could generate the metadata classes,  and thus have access to all of the great RIA Navigation Properties (Includes) and Validation capabilities.

After some searching, I discovered that in order to send anything back to the server from an ASP.Net page, this DomainService needs to be initialized. Hmm, what does that mean? More research.

I found an example here that explained how to Initialize a DomainService from an ASP.Net MVC site, and it appeared to be (and, in fact, was) exactly what I was looking for. It used a static ServiceExtension class and an implementation of the required ServiceProvider for creating a DomainServiceContext in a Web site using the current HttpContext. Here are the two relevant classes that I slightly modified:

using System;
using System.IO;
using System.Security.Principal;
using System.ServiceModel.DomainServices.Server;
using System.Web;
using System.Web.Hosting;

namespace CodeCamp2011.RIA.UI.Web
{
    public static class ServiceExtensions
    {
        /// <summary>
        /// Initializes the domain service by creating a new <see cref="DomainServiceContext"/>
        /// and calling the base DomainService.Initialize(DomainServiceContext) method.
        /// </summary>
        /// <typeparam name="TService">The type of the service.</typeparam>
        /// <param name="service">The service.</param>        
        /// <param name="operationType">The Operation Type.</param>
        /// <returns></returns>
        public static TService Initialize<TService>(this TService service, DomainOperationType operationType)
            where TService : DomainService
        {
            DomainServiceContext context = null;
            switch (operationType)
            {
                case DomainOperationType.Invoke:
                    {
                        context = CreateDomainServiceContextForInvoke();
                        break;
                    }
                case DomainOperationType.Query:
                    {
                        context = CreateDomainServiceContextForQuery();
                        break;
                    }
                case DomainOperationType.Submit:
                    {
                        context = CreateDomainServiceContextForSubmit();
                        break;
                    }
            }
            service.Initialize(context);
            return service;
        }

        private static DomainServiceContext CreateDomainServiceContextForInvoke()
        {
            var provider = new ServiceProvider(new HttpContextWrapper(GetHttpContext()));
            return new DomainServiceContext(provider, DomainOperationType.Invoke);
        }
        private static DomainServiceContext CreateDomainServiceContextForSubmit()
        {
            var provider = new ServiceProvider(new HttpContextWrapper(GetHttpContext()));
            return new DomainServiceContext(provider, DomainOperationType.Submit);
        }

        private static DomainServiceContext CreateDomainServiceContextForQuery()
        {
            var provider = new ServiceProvider(new HttpContextWrapper(GetHttpContext()));
            return new DomainServiceContext(provider, DomainOperationType.Query);
        }

        private static HttpContext GetHttpContext()
        {
            var context = HttpContext.Current;
#if DEBUG
            // create a mock HttpContext to use during unit testing...
            if (context == null)
            {
                var writer = new StringWriter();
                var request = new SimpleWorkerRequest("/", "/", String.Empty, String.Empty, writer);
                context = new HttpContext(request){ User = new GenericPrincipal(new GenericIdentity("debug"), null) };
            }
#endif
            return context;
        }
    }
}

and

using System;
using System.Security.Principal;
using System.Web;

namespace CodeCamp2011.RIA.UI.Web
{
    public class ServiceProvider : IServiceProvider
    {
        private HttpContextWrapper wrapper;

        public ServiceProvider(HttpContextWrapper wrapper)
        {
            this.wrapper = wrapper;
        }

        private HttpContext _httpContext;
        public object GetService(Type serviceType)
        {
            if (serviceType == null)
            {
                throw new ArgumentNullException("serviceType");
            }
            if (serviceType == typeof(IPrincipal))
                return this._httpContext.User;
            if (serviceType == typeof(HttpContextBase))
                return this._httpContext;
            return null;
        }
    }
}

I wanted to allow the ServiceExtension’s Initialize method to support the Operation Types that I might use, so I added a DomainOperationType parameter. I placed these two classes in a Helpers folder to tidy my project up a bit. With that in place, here’s the code for Saving an existing or new Person:

protected void Save_Click(object sender, EventArgs e)
{
    if (IsValidPerson)
    {
        var person = IsNewMode ? new Person { Id = -1 } : Person;
            // reference Colin Blair (again) regarding not null ID property using RIA from ASP.Net for an Insert: 
            // http://forums.silverlight.net/forums/p/116668/266040.aspx

        person.Email = EmailAddress.Text;
        person.Name = UserName.Text;
        person.PasswordHash = Encryption.ComputePasswordHash(Password1.Text);
        person.Title = JobTitle.Text;
        person.Website = WebSite.Text;
        person.Twitter = Twitter.Text;
        person.Blog = Blog.Text;
        person.Bio = Bio.Text;

        // // Wrap it in ChangeSetEntry
        var operation = IsNewMode ? DomainOperation.Insert : DomainOperation.Update;
        var changeEntry = new ChangeSetEntry { Entity = person, Operation = operation };
        var changeSetEntries = new List<ChangeSetEntry> { changeEntry };
        var changeSet = new ChangeSet(changeSetEntries);
        // Send the changes back to the server as an array of ChangeSetEntries
        domainService.Initialize(DomainOperationType.Submit);
        domainService.Submit(changeSet);

        // Get the entity we just saved so we have the new ID if Inserted
        Person = domainService.GetPersonByEmail(Person.Email);

        // TODO: some kind of popup indicating success here
        Password1.Text = string.Empty;
        Password2.Text = string.Empty;

    }
}
I hope you find this article useful, and, of course, if you have any questions or comments, fire away using the link below. Hoped this helps.

Bob Baker

Microsoft Visual Studio LightSwitch ONETUG Presentations and Sample posted

I had a great time at both the September and October meetings presenting A Lap around Microsoft Visual Studio LightSwitch (currently in Beta), a new and exciting Rapid Application Development environment for business analysts and Silverlight developers.

Microsoft Visual Studio LightSwitch gives you a simpler and faster way to create professional-quality business applications for the desktop, the web, and the cloud. LightSwitch is a new addition to the Visual Studio family. Visit this page often to learn more about this exciting product.

I have posted a zip file containing the two PowerPoint presentations, a database backup, the LightSwitch solution shown at the meetings with some brief instructions on my SkyDrive (which you can get from the link below). I hope you find these resources useful. As always, feel free to contact me if you have any further questions.

Bob Baker

Technorati Tags: ,,

Windows Phone 7 Developer Launch is October 12th

The Windows Phone Developer launch is next Tuesday, October 12th. That’s just a week from tomorrow! Click on the banner below to find out more. What’s really cool about this new platform is that it leverages your existing knowledge of Silverlight and the XNA Framework.

Windows Phone 7 Developer Launch

I have a Windows Phone 7 app ready. Do you?

Bob Baker

Windows 7 Upgrade boot configuration issue solved

I have already done a couple of Windows 7 upgrades over the past two weeks. I have found it to be the best upgrade Microsoft has ever produced. The upgraded computers run as fast (my perception) as if I had done a fresh installation. So I tackled my third upgrade this evening (a 32-bit Vista Ultimate on my daughters laptop), and ran into a glitch during the Setting and Programs copying stage. The Windows 7 installer stopped and reported that it could not continue because of a boot configuration problem as shown below.

clip_image002

At first, I thought it might be because I had selected a selective startup using msconfig as shown below.

image

I reset it to Normal startup and started the upgrade again. It stopped with the same issue. So I brought up System Properties, clicked the Startup and Recovery Settings button, and found there was no operating system to select in the dropdown! That can’t be good. So I did some research and found this article telling me how to run the bootrec tool from the Recovery Console to rebuild my Boot Configuration Data (BCD) store. But I didn’t even need to go that far. Right after booted my original Vista 32-bit DVD, and selected ‘Repair this computer’ from the initial preboot environment, I was offered the option to rebuild and restart. I did that, and once booted back into Vista, I found the OS listed in the System Properties, Startup and Recovery dialog. I don’t know how the computer got to that state in the first place, but the Windows 7 upgrade finished without further issues. Hope this helps.

Bob Baker

Technorati Tags: ,,

Two Collection Extensions for Unit Testing

Last week, an associate asked me a question about unit testing and mock business objects. She was writing unit tests against some rather complicated business entities with a deep object tree, and wanted to know if it was possible for her to get access to one of the lower level objects in a collection inside a parent object? She only wanted to create one mocked parent-level entity.

So, at the same time, I was perusing the Microsoft Composite Application (Prism) source code, and saw the CollectionExtensions class. Hmm, I love extension methods. What if I could write an extension to a generic collection that would find an object based on a passed-in property of the particular object she was looking for? Think of it as a FindByID kind of functionality for collections.

So I quickly wrote this (I know. I should have written the Unit Test first):

    /// <summary>
    /// Find an object in a collection.
    /// </summary>
    /// <typeparam name="T">Type of collection.</typeparam>
    /// <param name="collection">The collection to search.</param>
    /// <param name="isMatch">The Predicate to test to find the collection item.</param>
    /// <returns>The collection item.</returns>
    public static T GetItem<T>(this ICollection<T> collection,
                               Predicate<T> isMatch)
    {
        foreach (var item in collection)
        {
            if (isMatch(item))
                return item;
        }
        return null;
    }

And then I wrote a unit test to see how I did.

    [TestMethod]
    public void CanGetItemfromCollection()
    {
        var col = new Collection<string> {"Hello", "World"};
        var testItem = col.GetItem(c => c.Equals("World"));
        Assert.IsNotNull(testItem);
        Assert.AreEqual(testItem, "World");
    }

Nice. It passed. Now, I should extend this to support a more real-world example, say a person.

    /// <summary>
    /// Person class for simple mocking
    /// </summary>
    public class Person
    {
        public int ID { get; set; }
        public string Name { get; set; }
    }

So, I wrote another a unit test for the more complicated example:

    [TestMethod]
    public void CanGetItemfromCollectionByProperty()
    {
        var col = new Collection<Person>
    {
        new Person {ID = 1, Name = "Bob"},
        new Person {ID = 1, Name = "Jim"}
    };
        var testItem = col.GetItem(c => c.ID.Equals(2));
        Assert.AreEqual(testItem.Name, "Jim");
    }

Oh, the evils of copy and paste (a true Anti-Pattern)! This test failed with a NullReferenceException in the GetItem extension method. Do you see why? How fortunate for me that I accidently left two objects with the same ID. For without that, there would be no blog post today. Still, I corrected the typo and ran a successful test.

I could certainly do an Assert.IsNotNull on the return value, but then if it was null, I couldn't proceed in the test without seeing the failed test notes detailing the NullReferenceException in the called method. It did not break the running test; it only failed the test, and that's not really a bad thing at all, considering that a typo caused it.

If I threw a NullReferenceException myself in the extension method, that could be safer/saner for the developer depending on your point of view. That version as shown below still failed the test, but did not break it.

    /// <summary>
    /// Find an object in a collection.
    /// </summary>
    /// <typeparam name="T">Type of collection.</typeparam>
    /// <param name="collection">The collection to search.</param>
    /// <param name="isMatch">The Predicate to test to find the collection item.</param>
    /// <returns>The collection item.</returns>
    public static T GetItem<T>(this ICollection<T> collection, Predicate<T> isMatch)
    {
        foreach (var item in collection)
        {
            if (isMatch(item))
                return item;
        }
        throw new NullReferenceException("Collection object of type " + typeof(T) + 
                " not found for predicate provided and therefore, not initialized");
    }

Here's an edited version of what showed up in the Test Results:

Test method CanGetItemfromCollectionByProperty threw exception: System.NullReferenceException: Collection object of type CollectionExtensionsFixture+Person not found for predicate provided, and therefore, is not initialized.

So, a typo could break an app using the extension method at runtime versus the more desired compile time break. So, I tried to just return default(T) in the extension method instead of throwing an exception, and add Assert.IsNotNull(testItem) in the test method. I could do this safely and get a clean failure with or without the typo:

It would be good to figure out a way to new up a class<T> in the extension method if one isn't found with the provided predicate.

So, I walked down the hall to my neighborhood C# guru, and asked about the issue with the return value in my extension method. He quickly pointed out that I might need to consider writing specialized methods for value and reference types, and off I went to seek extension method nirvana.

At first, I tried to just add a where T : class to the basic GetItem extension method, but that made no difference; I was still getting a null reference back. I could fudge the Unit Test with an Assert.IsNotNull and then some if testing, but that's not the right way to go about it. Here's the 'abomination':

    [TestMethod]
    public void CanGetItemfromCollectionByProperty()
    {
        var col = new Collection<Person>
        {
            new Person {ID = 1, Name = "Bob"},
            new Person {ID = 2, Name = "Jim"}
        };
        var testItem = col.GetItem(c => c.ID.Equals(2));
        Assert.IsNotNull(testItem);
        if (testItem != null)
            Assert.AreEqual(testItem.Name, "Jim");
    }

Code smell, right? So I tried where T : new() in the extension method, but quickly got a compiler error, because in my basic single string test method, the extension method cannot handle the fact that the string object has no empty constructor. So, it looks like my guru was correct. Here's the answer:

    /// <summary>
    /// Find a value type object in a value-type collection.
    /// </summary>
    /// <typeparam name="T">Type of collection.</typeparam>
    /// <param name="collection">The collection to search.</param>
    /// <param name="isMatch">The Predicate to test to find the collection item.</param>
    /// <returns>The collection item.</returns>
    public static T GetValueItem<T>(this ICollection<T> collection, Predicate<T> isMatch)
    {
        foreach (var item in collection)
        {
            if (isMatch(item))
                return item;
        }
        return default(T);
    }

    /// <summary>
    /// Find a reference type object in a collection of reference types.
    /// </summary>
    /// <typeparam name="T">Type of collection.</typeparam>
    /// <param name="collection">The collection to search.</param>
    /// <param name="isMatch">The Predicate to test to find the collection item.</param>
    /// <returns>The collection item.</returns>
    public static T GetReferenceItem<T>(this ICollection<T> collection, Predicate<T> isMatch) 
where T : class, new() { foreach (var item in collection) { if (isMatch(item)) return item; } return new T(); }

For a value type, we simply return the default value of a new object (e.g., String.Empty, etc.). For a reference type, we simply return a new empty object. As long as there are no constraints preventing an empty new object, we'll be fine.

Testing now becomes straight-forward and gracefully supports typo's (and resulting nulls or empty objects) in either value type or reference type scenarios. Here are the revised test methods:

    [TestMethod]
    public void CanGetValueItemfromCollection()
    {
        var col = new Collection<string> { "Hello", "World" };
        var testItem = col.GetValueItem(c => c.Equals("World"));
        Assert.AreEqual(testItem, "World");
    }

    [TestMethod]
    public void CanGetReferenceItemfromCollectionByProperty()
    {
        var col = new Collection<Person>
        {
            new Person {ID = 1, Name = "Bob"},
            new Person {ID = 2, Name = "Jim"}
        };
        // known good by ID
        var testItem = col.GetReferenceItem(c => c.ID.Equals(2));
        Assert.AreEqual(testItem.Name, "Jim");
        // known does not exist
        testItem = col.GetReferenceItem(c => c.ID.Equals(3));
        Assert.IsNull(testItem.Name);
        // known good by Name
        testItem = col.GetReferenceItem(c => c.Name.Equals("Bob"));
        Assert.AreEqual(testItem.ID, 1);
    }

    /// <summary>
    /// Person class for simple mocking
    /// </summary>
    public class Person
    {
        /// <summary>
        /// ID
        /// </summary>
        public int ID { get; set; }
        /// <summary>
        /// Name
        /// </summary>
        public string Name { get; set; }
    }

Of course, your Person class comes from a mocked service or other separate class, right? Now if only I could mooch some of those Christmas cookies from my associate. And I hope your Christmas is joyful, and wish everyone a 2010 that is pronounced correctly.

Comments? Questions? Flames? Send me an email by clicking on my name below.

Bob Baker

Silverlight 3, .Net RIA Services, and common lookup data Redux

Introduction

Back in March, I blogged about this topic here, and while that post provided some sample code as a first draft approach which could lead you to much of the solution, I did not provide a workable sample. So many folks have helped me with Silverlight 3 and the exciting potential it has for quickly developing Line-of-Business (LOB) applications, and this topic appears to be such a frequent question on the Silverlight forums, that I felt I owed it to the community to revisit this topic in a more complete fashion.

So, today, we will dive into a complete end-to-end solution that highlights the following technologies:

  • Silverlight 3
  • .Net RIA Services
  • Databinding of lookup data from separate entities with Foreign Key relationships

Update: This post is based on the Silverlight 3 Beta and the .Net RIA Services May 2009 CTP. Unfortunately, it breaks with the RTM version of Silverlight 3 and the July CTP of RIA Services in the areas of data access and the DataForm control. I promise to update this article as soon as possible, or write an entirely new one.

There will be no coverage of the new Silverlight 3 features supporting navigation, user authentication, out-of-browser (OOB) experience, pixel shaders, video codecs, etc. Instead, we will build a simple application focusing on the unique challenges associated with combo boxes in Silverlight data consuming applications. Nor will I be delving into how to implement this using a Model-View-ViewModel (MVVM) pattern. To keep things simple, we will use the Northwind database in an Entity Framework model (provided in the download). The UI will consist of a DataGrid for selecting Products and a linked DataForm with the selected Product Details. Because the Northwind Products table is related to the Categories and Suppliers tables via declarative foreign keys, we will provide a combo box for each that is bound to the equivalent foreign key on the Categories and Suppliers tables. The key difference is that we will display the human-readable name of the currently selected Category of Supplier in the dropdown, as well as a list of same when in edit mode and selecting a Category or Supplier. We won’t be binding to the actual child entities, but instead to IDictionary (key, value) implementations of them, which we will load immediately and cache on the client for performance.

I must extend a special thanks to Luke Longley of Microsoft and Chris Anderson (the original article from which I got the inspiration for the IDictionary refactoring was posted here), as well as numerous other folks on the Silverlight forums for their kind assistance in helping me understand what needed to be done to solve this UI challenge.

There is a complete sample project here for you to download and experiment with. Of course, you will need the Silverlight 3 Beta, .Net RIA Services 1.0 Beta, and Silverlight 3 Beta Tools for Visual Studio mentioned above. All of these are available here. You should have SQL Server Express 2005 installed at a minimum, but this project will also work with SQL Server 2005 or any version of SQL Server 2008 as well; you’ll just have a little re-configuration to do.

Getting to work

So, to begin, open Visual Studio 2008, and create a new Silverlight 3 Application (I called it SL3ComboBoxDemo). The Northwind database (modified by John Papa for the Entity Framework) is supplied in the Web project’s App_Data directory, so if you want to start with a fresh project instead of opening the sample, simply copy it there. In your Web project, add a new ADO.Net Entity Framework item and point it at your local NorthwindEF database. I named my model NorthwindEF_Model, and selected the Categories, Products and Suppliers tables. I named the connection string NorthwindEF_Entities and finished up.

Again in your Web project, add a new DomainService item. I named mine NorthwindEFService and checked all checkboxes to enable editing and metadata generation on the three entities in the model.

If you’re unfamiliar with the preceding two steps, simply refer to the first several chapters of the Microsoft .Net RIA Services Overview document. I then copied the code out of the automatically generated Test aspx file in the Web project and pasted it over top of the code in Default.aspx starting after the initial Page tag. I then deleted both test files (.aspx and .html).

Your solution (minus the items we’re going to add) should now look something like this:

image

Figure 1. SL3ComboBoxDemo Solution

Note that I have added the ErrorWindow files from the Silverlight Business Application template to our Silverlight 3 client project. We may need this, but hopefully not today <g>. Verify that SL3ComboBoxDemo.Web is your startup project and that Default.aspx is the set as the Start Page.

Open up the NorthwindEFService.metadata.cs file and make some additions in the ProductsMetadata as follows:

    [Include]
    public Categories Categories;
    [Include]
    public Suppliers Suppliers;

Now Build the solution. This generates the RIA data access code on the SL3ComboBoxDemo client.

The User Experience

Let’s add some UI and code-behind. Again, to keep things simple, we’re just going to add our UI and logic to the pages created by the project template: MainPage.xaml and MainPage.xaml.cs. In MainPage.xaml, add the following namespace references:

xmlns:data="clr-namespace:System.Windows.Data;
      assembly=System.Windows.Ria.Controls"
xmlns:dataControls="clr-namespace:System.Windows.Controls;
      assembly=System.Windows.Controls.Data.DataForm"
xmlns:ods="clr-namespace:System.Windows.Controls;
      assembly=System.Windows.Ria.Controls"
xmlns:datagrid="clr-namespace:System.Windows.Controls;
      assembly=System.Windows.Controls.Data" 
xmlns:converter="clr-namespace:SL3ComboBoxDemo.ValueConverters"

I set the width and height of the UserControl to 800 and 390 respectively, and this seemed to work for this application. Replace everything else from the outermost <Grid … declaration to the end of that Grid with this:

<Grid x:Name="LayoutRoot" Background="White" 
    HorizontalAlignment="Stretch" VerticalAlignment="Stretch">
  <Grid.Resources>
    <converter:CurrencyConverter x:Key="CurrencyConverter" />
    <converter:DictionaryConverter x:Key="DictionaryConverter" />
  </Grid.Resources>
  <Border BorderBrush="#AA000000" BorderThickness="2" CornerRadius="5" 
          Margin="5,5,5,5" VerticalAlignment="Stretch" 
HorizontalAlignment="Stretch" > <StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"> <TextBlock Text="Silverlight 3 DataForm Combo Box Binding Demonstration" HorizontalAlignment="Center"/> <StackPanel> <ods:DomainDataSource x:Name="dds" LoadMethodName="LoadProducts" AutoLoad="True" LoadSize="42" LoadedData="dds_LoadedData" LoadingData="dds_LoadingData" SubmittedChanges="dds_SubmittedChanges" DataContext="BusinessManager.Context"> <ods:DomainDataSource.SortDescriptors> <data:SortDescriptor PropertyPath="ProductName" Direction="Ascending" /> </ods:DomainDataSource.SortDescriptors> <ods:DomainDataSource.FilterDescriptors> <data:FilterDescriptorCollection> <data:FilterDescriptor PropertyPath="ProductName" Operator="Contains"> <data:ControlParameter PropertyName="Text" RefreshEventName="TextChanged" ControlName="nameFilterBox"> </data:ControlParameter> </data:FilterDescriptor> </data:FilterDescriptorCollection> </ods:DomainDataSource.FilterDescriptors> </ods:DomainDataSource> <StackPanel Orientation="Horizontal" Margin="7,7,0,0"> <StackPanel VerticalAlignment="Top"> <StackPanel Orientation="Horizontal" Margin="0,0,0,5"
VerticalAlignment="Top"> <TextBlock Text=" Find: " VerticalAlignment="Center"/> <TextBox x:Name="nameFilterBox" Width="234" Height="24" /> </StackPanel> <datagrid:DataGrid x:Name="dataGrid1" Width="270" Height="266" HorizontalAlignment="Left" VerticalAlignment="Top" AutoGenerateColumns="False" IsReadOnly="True" SelectionChanged="dataGrid1_SelectionChanged" ItemsSource ="{Binding Data, ElementName=dds}"> <datagrid:DataGrid.Columns> <datagrid:DataGridTextColumn Header="Product Name" Binding="{Binding ProductName}" CanUserSort="True" /> </datagrid:DataGrid.Columns> </datagrid:DataGrid> <dataControls:DataPager x:Name="pager1" Width="270" PageSize="10" HorizontalAlignment="Left" Source="{Binding Data, ElementName=dds}"> </dataControls:DataPager> </StackPanel> <Grid> <Grid.RowDefinitions> <RowDefinition Height="Auto"/> <RowDefinition Height="*" /> <RowDefinition Height="*"/> </Grid.RowDefinitions> <StackPanel Grid.Row="0" Margin="3,0,0,0"> <dataControls:DataForm x:Name="dataForm1" Margin="5,0,5,0"
MinHeight="280" AutoEdit="False" AutoCommit="False" Width="480" VerticalAlignment="Top" CommandButtonsVisibility="All" Header="Product Details" CurrentItem="{Binding ElementName=dataGrid1, Path=SelectedItem}" DeletingItem="dataForm1_DeletingItem" ItemEditEnded="dataForm1_ItemEditEnded" AddingItem="dataForm1_AddingItem" AutoGenerateFields="False" CanUserAddItems="True"
CanUserDeleteItems="True"> <dataControls:DataForm.Fields> <dataControls:DataFormFieldGroup Orientation="Horizontal" > <dataControls:DataFormTextField FieldLabelPosition="Top" FieldLabelContent="Product Name " Binding="{Binding ProductName, Mode=TwoWay }" /> <dataControls:DataFormTextField FieldLabelPosition="Top" FieldLabelContent="Price " Binding="{Binding UnitPrice, Mode=TwoWay, Converter={StaticResource CurrencyConverter} }" /> </dataControls:DataFormFieldGroup> <dataControls:DataFormFieldGroup Orientation="Horizontal" > <dataControls:DataFormTextField FieldLabelPosition="Top" FieldLabelContent="Units In Stock " Binding="{Binding UnitsInStock, Mode=TwoWay }" /> <dataControls:DataFormTextField FieldLabelPosition="Top" FieldLabelContent="Units On Order " Binding="{Binding UnitsOnOrder, Mode=TwoWay }" /> <dataControls:DataFormTextField FieldLabelPosition="Top" FieldLabelContent="Reorder Level" Binding="{Binding ReorderLevel, Mode=TwoWay }" /> </dataControls:DataFormFieldGroup> <dataControls:DataFormSeparator/> <dataControls:DataFormFieldGroup Orientation="Horizontal"> <dataControls:DataFormComboBoxField x:Name="cboCategories" FieldLabelContent="Category:" DisplayMemberPath="Value" Binding="{Binding CategoryID, Mode=TwoWay, Converter={StaticResource DictionaryConverter}, ConverterParameter='ProductDictionaries.CategoriesLookup' }"/> <dataControls:DataFormComboBoxField x:Name="cboSuppliers" FieldLabelContent="Supplier:" DisplayMemberPath="Value" Binding="{Binding SupplierID, Mode=TwoWay, Converter={StaticResource DictionaryConverter}, ConverterParameter='ProductDictionaries.SuppliersLookup' }" /> </dataControls:DataFormFieldGroup> <dataControls:DataFormFieldGroup Orientation="Horizontal"> <dataControls:DataFormCheckBoxField FieldLabelContent="Discontinued?" FieldLabelPosition="Left" Binding="{Binding Discontinued, Mode=TwoWay}" /> <dataControls:DataFormDateField FieldLabelPosition="Left" FieldLabelContent="Discontinued Date:" Binding="{Binding DiscontinuedDate, Mode=TwoWay}" SelectedDateFormat="Short" /> </dataControls:DataFormFieldGroup> </dataControls:DataForm.Fields> </dataControls:DataForm> </StackPanel> <StackPanel Grid.Row="1" Orientation="Horizontal" Margin="0,5,0,5"> <Button x:Name="saveButton" Width="100" Height="30" Margin="8,4,0,5" Content="Submit Changes" IsEnabled="False" Click="submitButton_Click" ToolTipService.ToolTip="Submit all changes to the server"/> </StackPanel> </Grid> </StackPanel> </StackPanel> </StackPanel> </Border> </Grid>

If you’ve gone through some of the RIA walk-throughs and samples (highly recommended), there’s not very much new here. So I’ll direct your attention to the binding of the DataFormComboBoxField, cboCategories:

<dataControls:DataFormComboBoxField x:Name="cboCategories" 
              FieldLabelContent="Category:" DisplayMemberPath="Value" 
              Binding="{Binding CategoryID, Mode=TwoWay, 
              Converter={StaticResource DictionaryConverter},
              ConverterParameter='ProductDictionaries.CategoriesLookup' }"/>

What we will be doing here is binding to the CategoryID in the Products entity, and using a Dictionary Converter to manage the relationship between what is in Products and what it points to in Categories. To do this, I created a Dictionary class for all lookup dictionaries associated with Products (the thought being that you might choose to partition different sets of lookup data into difference Dictionaries) as:

public static class DictionaryCache
{
    public static ProductDictionaries ProductDictionaries { get; set; }
}

The actual ProductDictionaries object looks like this:

public class ProductDictionaries
{
    public Dictionary<int, string> CategoriesLookup { get; set; }
    public Dictionary<int, string> SuppliersLookup { get; set; }
}

Now, let’s get our context loaded up. Because I typically have a lot of lookup tables, I decided to implement a singleton Context that the entire application can reference. That looks like this (BusinessManager.cs):

public static class BusinessManager 
{
    private static NorthwindEFContext context = new NorthwindEFContext();

    public static NorthwindEFContext Context
    {
        get
        {
            return context;
        }
    }
}

Now in our code-behind, we can start to get our data loaded and bound. In MainPage.xaml.cs make sure you have the following using statements:

using System.Windows.Controls;
using System.Windows.Ria.Data;
using SL3ComboBoxDemo.Web;
using SL3ComboBoxDemo.Model;
using SL3ComboBoxDemo.Util;

Now, before the constructor, declare our data context and a bool useful for determining if we can skip some code execution once we’ve performed it once (due to the asynchronous nature of Silverlight data access):

private NorthwindEFContext Context = BusinessManager.Context;
private static bool formIsBound = false;

In the constructor, set up the following immediately after the InitializeComponent call:

dds.DomainContext = Context;
dataForm1.ItemEditEnded += 
new EventHandler<DataFormItemEditEndedEventArgs>(dataForm1_ItemEditEnded); pager1.PageIndexChanged +=
new EventHandler<EventArgs>(pager1_PageIndexChanged); this.Loaded += new RoutedEventHandler(MainPage_Loaded);

Down in MainPage_Loaded, we then wire up a Context.Loaded EventHandler and start loading data:

void MainPage_Loaded(object sender, RoutedEventArgs e)
{
    Context.Loaded += new EventHandler<LoadedDataEventArgs>(Context_Loaded);
    LoadFromServer();
}

The reason I used a LoadFromServer() call is in case I wanted to wire up Isolated Storage at some point later (in fact, I do this in a LOB application I am building). LoadFromServer() hits the Context up for only Categories and Suppliers, since the Products are set to AutoLoad in the markup:

private void LoadFromServer()
{
    Context.LoadCategories();
    Context.LoadSuppliers();
}

When Context_Loaded fires, we have to decide what we have and what to do with it. I decided that here is where I wanted to populate my ProductDictionaries if I had all of the data that I needed (since Context_Loaded is fired at least once for each Entity loaded). The way I ensure I have what I need to proceed is to check the counts of all the Entities I am loading.

void Context_Loaded(object sender, System.Windows.Ria.Data.LoadedDataEventArgs e)
{
    if (Context.Products.Count > 0
            && Context.Categories.Count > 0
            && Context.Suppliers.Count > 0)
    {
        if (DictionaryCache.ProductDictionaries == null)
            DictionaryCache.ProductDictionaries = GetProductDictionaries();
        SetupComboBoxes();
    }
}

GetProductDictionaries() does some LinqToSQL magic with our loaded entities on the client side to populate our ProductDictionaries object:

public ProductDictionaries GetProductDictionaries()
{
    var productDictionaries = new ProductDictionaries();

    var categoryQuery =
        from c in Context.Categories
        orderby c.CategoryName
        select c;
    productDictionaries.CategoriesLookup = 
        categoryQuery.ToDictionary(c => c.CategoryID,
                                   c => c.CategoryName);
    var supplierQuery =
        from s in Context.Suppliers
        orderby s.CompanyName
        select s;
    productDictionaries.SuppliersLookup = 
        supplierQuery.ToDictionary(s => s.SupplierID,
                                   s => s.CompanyName);
    return productDictionaries;
}

Now for the final piece of magic. Because the DataFormComboBoxField is not visible to us via normal x:Name identity (even though stubborn me put one on each <g> – more as a note to self), we’ll have to iterate through the controls in the DataForm and find the one bound to the ID we want to find, so that we can set the ItemsSource of each Combo Box correctly. That method is in the Util folder as DataFormBinding.GetFieldByBindingPath(). Back in the MainPage, we return to SetupComboBoxes (here’s where that bool declared in the class comes in handy):

void SetupComboBoxes()
{
    if (!formIsBound)
    {
        if (Context.Categories.Count > 0)
        {
            DataFormComboBoxField cboCategories =
                DataFormBinding.GetFieldByBindingPath(dataForm1, "CategoryID") 
                as DataFormComboBoxField;
            if (cboCategories.ItemsSource == null)
            {
                cboCategories.ItemsSource = 
                    DictionaryCache.ProductDictionaries.CategoriesLookup;
            }
        }
        if (Context.Suppliers.Count > 0)
        {
            DataFormComboBoxField cboSuppliers =
                DataFormBinding.GetFieldByBindingPath(dataForm1, "SupplierID") 
                as DataFormComboBoxField;
            if (cboSuppliers.ItemsSource == null)
            {
                cboSuppliers.ItemsSource = 
                    DictionaryCache.ProductDictionaries.SuppliersLookup;
            }
        }
        if (Context.Products.Count > 0
            && Context.Categories.Count > 0
            && Context.Suppliers.Count > 0)
        {
            formIsBound = true;
SetupDataForm(); } }
else
SetupDataForm(); }

SetupDataForm does the final setting of current item/index setting on the DataGrid and DataForm. I did not use any server activity method to indicate server activity, but you can easily wrap this Activity Control around the DataGrid, and have an indication when the data is being fetched. You can, of course, add any styling or theming that you want. Again, the focus on this post was the data! And here’s what we have built:

image

Figure 2. SL3ComboBoxDemo in action

The first time you load the application, it may take a short while to load the data, as your SQL Server Express User Instance is created, but any sessions thereafter will be fairly fast. I did not implement complete code to allow additions or deletions of Products in order to keep this article as simple as possible, but there is already some plumbing in place in the sample code. And so, I leave that to you as an exercise, dear reader. However, you can certainly edit the Products to your heart’s content.

Wrapping Up

So, there you have it – a quick tour, but now supported by a complete code solution, of how to bind lookup data to combo boxes on Silverlight 3 DataForms. If you look closely into the sample code, you will also find implementations of some other tips I learned on the Silverlight Forums. And finally, if you see any areas where I could improve my approach, please do not hesitate to let me know. I hope this helps you in your Silverlight development efforts.

Bob Baker

P.S. I went to Orlando CodeCamp on March 28, 2009. Did you?