Welcome to Pete Brown's 10rem.net

First time here? If you are a developer or are interested in Microsoft tools and technology, please consider subscribing to the latest posts.

You may also be interested in my blog archives, the articles section, or some of my lab projects such as the C64 emulator written in Silverlight.

(hide this)

The MIX10 Trivia Application - Silverlight, WCF, ASP.NET

Pete Brown - 12 March 2010

Most Microsoft events have a few custom applications built for them. You never really hear much about them, and perhaps don't even think about them as custom apps, but there they are, in the keynote showing tweets, or before sessions (in this case) showing trivia questions.

MIX has been my favorite conference since I first attended in 2006. Last year I had a blast and entered the showoff with my C64 emulator in Silverlight. This will be the first year I get to contribute something real, albeit small, to each of the breakout sessions in the conference. This is also the first year I'll attend as a Microsoft employee.

One of the first projects I worked on when I joined Microsoft in October 2009, was a small PDC trivia application. This Silverlight 3 + ASP.NET 4 application ran in the breakout rooms in between sessions. Scott Hanselman and I sourced over 200 trivia questions - some Microsoft-related, some not - to populate the app.

And, since I compiled and ultimately entered all the questions and answers, you just know there's a fair bit of commodore and retro trivia in there :)

Here's what the PDC09 version looked like, following the PDC09 presentation theme running at 1280x800. (need to support both widescreen and 1024x768 for this app)

image  image

Today, I updated the application for MIX10. As was the case at PDC, this will run in the breakout rooms in between sessions.

New MIX10 Version

Here's what the new MIX10 version looks like. Unlike PDC, MIX doesn't have an enforced style to the content, so I really had nothing specific to match other than the sites themselves. I may tweak the UI a bit more before I install it on Sunday, but this is what it currently looks like when run at 1024x768:

  image  image

The changes are not huge. I took some styling hints from the MIX Online event site by using the same colors and creating a brown textured background (photoshop filter->noise), and moved the countdown to center screen, but otherwise tried not to create a mess by pretending to be a designer :)

The real stars in the rooms are the speakers. What I've created is background for the room while they set up. I was tempted to try something very Saul Bass like. Maybe if I can get an app into the keynote room next year :)

Overall Architecture

The application architecture is straight-forward, and fairly typical for a Silverlight app. The Silverlight client communicates with the server via a Silverlight-enabled WCF Service. That's just an easy WCF service implementation that communicates over binary SOAP.

 

image

The Silverlight client itself uses the MVVM or ViewModel pattern internally. Let's take a look at that.

Silverlight Client Architecture

The Silverlight client architecture is typical for a lightweight MVVM-based client. The main page is a shell that contains the full screen button as well as an instance of the Q&A control. The Q&A control binds to the view-model which in turn makes calls to the repository to get the data.

image

The bits in play are:

  • Main Page: Shell that holds everything else
  • Question and Answer Control: Has storyboards and datacontext
  • Question Control: Visuals for question
  • Answer Control: Visuals for answer
  • ViewModel: Q&A Control's interface to the rest of the world
  • Shared Entity: The Q&A entity, shared between the client and WCF
  • Repository: Interface to my data.
  • WCF Client Proxy: generated client proxy for the WCF service

The public interface for the view-model looks like this:

public class TriviaViewModel : INotifyPropertyChanged 
{
    public event EventHandler TriviaLoaded;
    public ObservableCollection<TriviaItem> TriviaItems;
    public TriviaViewModel(TriviaRepository repository);
    public TriviaItem CurrentTriviaItem;

    public void MoveFirst();
    public void MoveNext();
    public void MovePrevious();
    public bool IsLoaded;

    #region INotifyPropertyChanged Members

    public event PropertyChangedEventHandler PropertyChanged;

    #endregion
}

(I'm not using any interfaces in this app; I just removed the private methods and method bodies so you could see the class signature.)

Starting it up and Randomizing Questions

When the room monitor hits the url for the site, they get a page with a single button, asking them to run full-screen. In the background, it downloads the full list of trivia Q&A. Once they click that button, the app goes into full-screen mode, randomizes the questions, and starts up a storyboard that runs for each question. The randomization of the questions is an interesting little bit of linq sitting in my view-model:

// randomize the order of the trivia questions
Random rand = new Random();
var randomizedList = from TriviaItem item in _repository.TriviaItems
                     orderby rand.Next()
                     select item;

The randomized questions are then loaded into an ObservableCollection which is used for binding. Technically, since I'm using a CurrentItem and MoveNext type approach, the observable collection is redundant. I could have kept that as a List<T>.

Automatic Navigation

The application is essentially a storyboard that repeats until someone stops it. The storyboard steps are:

  1. Zoom the Question in from the left
  2. Show the image, if any (image slowly moves on a 3d plane projection as well)
  3. Wait
  4. Show the countdown (5..4..3..2..1)
  5. Minimize the image, if any.
  6. Zoom the Answer in from the right and minimize the image

To move from question to question, I use a event handler in the code-behind to catch the Storyboard completed event, move to the next question, and restart the storyboard:

// storyboard completed event
void ShowQuestionAndAnswer_Completed(object sender, EventArgs e)
{
    _viewModel.MoveNext();
    ShowQuestionAndAnswer.Begin();
}

Manual Navigation

I wanted to provide the ability to skip questions, move back to the previous question etc. Since I'm using Silverlight 3 for this application, I couldn't use any built-in commanding. Instead, I just forward keypress events to the MoveFirst, MoveNext and MovePrevious methods. The code looks like this:

void TriviaQuestionAndAnswer_KeyUp(object sender, KeyEventArgs e)
{
    // arrow keys move between questions

    switch (e.Key)
    {
        case Key.Right:
        case Key.Down:
        case Key.PageDown:
            ShowQuestionAndAnswer.Stop();
            _viewModel.MoveNext();
            ShowQuestionAndAnswer.Begin();
            break;
        case Key.Left:
        case Key.Up: 
        case Key.PageUp:
            ShowQuestionAndAnswer.Stop();
            _viewModel.MovePrevious();
            ShowQuestionAndAnswer.Begin();
            break;
    }
}

What Exactly is in the Code-Behind?

I love the viewmodel pattern, but I'm not going to go nuts trying to have an empty code-behind unless I think that will provide real value to the application. I'm not in the "your view model must be empty" camp.

Like all of the rest of you, I had to write this application under time constraints using a specific technology (Silverlight 3, as SL4 wouldn't be on the PDC09 presentation machines). To that end, I put UI logic into the code-behind when I felt it made sense. Specifically:

  • Keyboard event handling
  • View model instance creation
  • Data context setting (to the view model)
  • Image failed event handler
  • Storyboard Completed event handler
  • A show function that starts the storyboard for the first time.

With commanding in Silverlight 4, you can move some of those things into the ViewModel, but overall, I think I made good choices about what went in the code-behind vs. what went into the view model. More importantly, I made informed and educated choices about them. Much of the backlash against code-behind code comes because many folks put code in there without learning about the alternatives and trade-offs.

Fonts

PDC09 had its own signature font named "squared"; you could see it all over the PDC artwork and the site. For that reason, the PDC version of the application contained the embedded version of the PDC font:

FontFamily="/PeteBrown.PdcTrivia;Component/Fonts/Fonts.zip#Squared"

The font itself was stored in the fonts folder in its native TTF format. I used Expression Blend to do the UI and the font embedding, so the Build Action for the font file is set to BlendEmbeddedFont. That build action takes care of packaging, subsetting (if you opt for it) and the overall embedding process.

The MIX10 version uses standard fonts: Georgia and Arial.

Countdown

The countdown is actually composed of five different textblocks. I did this so I could show the current one while transforming the previous one and fading it out. For example, the "3" pops up while the "4" is fading out and doing a scale transform to a zero size.

There are other ways to accomplish this that may result in a few fewer elements, but this was the easiest way to do it. It's pretty flexible too since all the logic is in the storyboard itself.

Server

The server is our data repository. Other than some up-front formatting, and the maintenance application, there's not much logic there.

Maintenance App

For grins, I tried out asp.net 4 dynamic data. DD allowed me to create a very simple maintenance application without worrying about creating individual screens. That was the first time I used DD, and my experience with it was pretty decent. Interestingly, it built on several things I learned when working with WCF RIA Services, the metadata approach being the most obvious.

Here's the main entity metadata, with some markup telling the UI what types of controls to use for the editor page.

public class QuestionAndAnswerMetadata
{
    [UIHint("FileUpload")]
    public object QuestionImage { get; set; }

    [ReadOnly(true)]
    public object DateAdded { get; set; }

    [ReadOnly(true)]
    public object UserName { get; set; }

    [StringLength(200)]
    [UIHint("MultilineText")]
    public object Question { get; set; }

    [StringLength(200)]
    [UIHint("MultilineText")]
    public object Answer { get; set; }

}

 

The Silverlight client interacts with a simple WCF service on the same server. I used a purpose-built entity, cross-compiled for full .NET and Silverlight using the dual-project shared file approach, for the messaging.

namespace PeteBrown.PdcTrivia.Entities
{
    public class TriviaItem
    {
        public string Question { get; set; }
        public string Answer { get; set; }
        public string ImageUri { get; set; }
    }
}

Let's talk about the service next.

The WCF Service

The WCF service pulls back all the trivia entries, as well as the url for the images (see next section). It translates from the dynamic data entity to the TriviaItem entity that is shared with the Silverlight client. Here's the code:

[OperationContract]
public List<TriviaItem> GetTrivia()
{
    List<TriviaItem> results = new List<TriviaItem>();
    
    string imageStringFormat = HttpContext.Current.Request.Url.ToString().Replace("Services/TriviaService.svc", "") + "GetImage.aspx?id={0}";

    PdcTriviaDataEntities context = new PdcTriviaDataEntities();
    foreach (QuestionAndAnswer qa in context.QuestionsAndAnswers.ToList<QuestionAndAnswer>())
    {
        TriviaItem item = new TriviaItem();
        item.Question = qa.Question;
        item.Answer = qa.Answer;
        if (qa.QuestionImage != null)
            item.ImageUri = string.Format(imageStringFormat, qa.Id);
        else
            item.ImageUri = string.Empty;

        results.Add(item);
    }

    return results;
}

 

Images: Hrefs to Blobs

I store the images in SQL Server as a blob field. This made it easy to move the application around to different machines, as well as use dynamic data to update the field.

Silverlight doesn't understand SQL Server, obviously, so I created a .aspx page that will return the image. Another option would have been a httmhandler, but a regular old .aspx page requires the fewest configuration touch points.

The asp.net code to pull back an image from SQL Server and return it to Silverlight looks like this:

protected void Page_Load(object sender, EventArgs e)
{
    string idqs = Request.QueryString["id"];

    if (string.IsNullOrEmpty(idqs))
    {
        Response.Write("Missing parameter 'id'.");
        return;
    }

    int id;
    if (!int.TryParse(idqs, out id))
    {
        Response.Write("Invalid id.");
        return;
    }


    PdcTriviaDataEntities context = new PdcTriviaDataEntities();
    Object entity = null;
    IEnumerable<KeyValuePair<string, object>> keyValues = new KeyValuePair<string, object>[] { new KeyValuePair<string, object>("Id", id) };
    if (context.TryGetObjectByKey(new EntityKey("PdcTriviaDataEntities.QuestionsAndAnswers", keyValues), out entity))
    {
        QuestionAndAnswer qa = (QuestionAndAnswer)entity;

        if (qa.QuestionImage == null)
        {
            Response.Write("No image in that item.");
            return;
        }


        // write the image

        Response.Buffer = true;
        Response.Charset = "";
        Response.ContentType = "image/jpeg";
        Response.BinaryWrite(qa.QuestionImage);
        Response.Flush();
        Response.End();

    }
    else
    {
        Response.Write("No such item.");
        return;
    }

}

That allowed me to have the entity contain only the Uri (which is this page plus an id), keeping the initial Silverlight WCF payload size small.

An interesting kludge is line 37, highlighted above. Setting the content type to image/jpeg works for jpegs, but it turns out it also works for the pngs I uploaded.

Hosting

The application doesn't require a lot of horsepower. Since I'm using Silverlight on the client, the server needs only be able to serve up the Silverlight app, the initial burst of data, and handle any image requests. So, it's running on a workstation with Server 2008, IIS7, ASP.NET 4 RC, SQL Server 2008 and (for emergency repairs) Visual Studio 2010 installed. As I recall, it's a dual proc machine with 4gig RAM running the 64bit version of the OS.

 

That's my little "behind the scenes" for this app. If you see it running at MIX, see how many of the trivia questions you can answer.

           
posted by Pete Brown on Friday, March 12, 2010
filed under:            

3 comments for “The MIX10 Trivia Application - Silverlight, WCF, ASP.NET”

  1. Mike Greenwaysays:
    Love it.

    I also believe that the codebehind is the perfect place for UI code, that is, code that animates the UI and isn't bound to data. The only testing method is the human mind-eye, dose it look and feel correct.

    thanks

Comment on this Post

Remember me