Amazon.com Widgets CodeProject

WilliaBlog.Net

I dream in code

About the author

Robert Williams is an internet application developer for the Salem Web Network.
E-mail me Send mail
Code Project Associate Logo
Ads by Lake Quincy Media

Recent comments

Authors

Disclaimer

The opinions expressed herein are my own personal opinions and do not represent my employer's view in anyway.


Mock a database repository using Moq

The concept of unit testing my code is still fairly new to me and was introduced when I started writing applications with the Microsoft MVC Framework in Visual Studio 2008. Intimidated somewhat by the Moq library's heavy reliance on lambdas, my early tests used full Mock classes that I would write myself, and which implemented the same interface as my real database repositories. I'd only write the code for the methods I needed, all other methods would simply throw a "NotImplementedException". However, I quickly discovered that the problem with this approach is that whenever a new method was added to the interface, my test project would no longer build (since the new method was not implemented in my mock repository) and I would have to manually add a new method that threw another "NotImplementedException". After doing this for the 5th or 6th time I decided to face my fears and get to grips with using the Moq library instead. Here is a simple example, of how you can mock a database repository class using the Moq library.

Let's assume that your database contains a table called Product, and that either you or Linq, or LLBLGen, or something similar has created the following class to represent that table as an object in your class library:

The Product Class

namespace MoqRepositorySample

{

    using System;

 

    public class Product

    {

        public int ProductId { get; set; }

 

        public string Name { get; set; }

 

        public string Description { get; set; }

 

        public double Price { get; set; }

 

        public DateTime DateCreated { get; set; }

 

        public DateTime DateModified { get; set; }

    }

}

 

Your Product Repository class might implement an interface similar to the following, which offers basic database functionality such as retrieving a product by id, by name, fetching all products, and a save method that would handle inserting and updating products.

 

The IProductRepository Interface

 

namespace MoqRepositorySample

{

    using System.Collections.Generic;

 

    public interface IProductRepository

    {

        IList<Product> FindAll();

 

        Product FindByName(string productName);

 

        Product FindById(int productId);

 

        bool Save(Product target);

    }

}

 

The test class that follows demonstrates how to use Moq to set up a mock Products repository based on the interface above. The unit tests shown here focus primarily on testing the mock repository itself, rather than on testing how your application uses the repository, as they would in the real world.

 

Microsoft Unit Test Class

 

namespace TestProject1

{

    using System;

    using System.Collections.Generic;

    using System.Linq;

    using Microsoft.VisualStudio.TestTools.UnitTesting;

 

    using Moq;

 

    using MoqRepositorySample;

 

    /// <summary>

    /// Summary description for UnitTest1

    /// </summary>

    [TestClass]

    public class UnitTest1

    {

        /// <summary>

        /// Constructor

        /// </summary>

        public UnitTest1()

        {

            // create some mock products to play with

            IList<Product> products = new List<Product>

                {

                    new Product { ProductId = 1, Name = "C# Unleashed", Description = "Short description here", Price = 49.99 },

                    new Product { ProductId = 2, Name = "ASP.Net Unleashed", Description = "Short description here", Price = 59.99 },

                    new Product { ProductId = 3, Name = "Silverlight Unleashed", Description = "Short description here", Price = 29.99 }

                };

 

            // Mock the Products Repository using Moq

            Mock<IProductRepository> mockProductRepository = new Mock<IProductRepository>();

 

            // Return all the products

            mockProductRepository.Setup(mr => mr.FindAll()).Returns(products);

 

            // return a product by Id

            mockProductRepository.Setup(mr => mr.FindById(It.IsAny<int>())).Returns((int i) => products.Where(x => x.ProductId == i).Single());

 

            // return a product by Name

            mockProductRepository.Setup(mr => mr.FindByName(It.IsAny<string>())).Returns((string s) => products.Where(x => x.Name == s).Single());

 

            // Allows us to test saving a product

            mockProductRepository.Setup(mr => mr.Save(It.IsAny<Product>())).Returns(

                (Product target) =>

                {

                    DateTime now = DateTime.Now;

 

                    if (target.ProductId.Equals(default(int)))

                    {

                        target.DateCreated = now;

                        target.DateModified = now;

                        target.ProductId = products.Count() + 1;

                        products.Add(target);

                    }

                    else

                    {

                        var original = products.Where(q => q.ProductId == target.ProductId).Single();

 

                        if (original == null)

                        {

                            return false;

                        }

 

                        original.Name = target.Name;

                        original.Price = target.Price;

                        original.Description = target.Description;

                        original.DateModified = now;

                    }

 

                    return true;

                });

 

            // Complete the setup of our Mock Product Repository

            this.MockProductsRepository = mockProductRepository.Object;

        }

 

        /// <summary>

        /// Gets or sets the test context which provides

        /// information about and functionality for the current test run.

        ///</summary>

        public TestContext TestContext { get; set; }

 

        /// <summary>

        /// Our Mock Products Repository for use in testing

        /// </summary>

        public readonly IProductRepository MockProductsRepository;

 

        /// <summary>

        /// Can we return a product By Id?

        /// </summary>

        [TestMethod]

        public void CanReturnProductById()

        {

            // Try finding a product by id

            Product testProduct = this.MockProductsRepository.FindById(2);

 

            Assert.IsNotNull(testProduct); // Test if null

            Assert.IsInstanceOfType(testProduct, typeof(Product)); // Test type

            Assert.AreEqual("ASP.Net Unleashed", testProduct.Name); // Verify it is the right product

        }

 

        /// <summary>

        /// Can we return a product By Name?

        /// </summary>

        [TestMethod]

        public void CanReturnProductByName()

        {

            // Try finding a product by Name

            Product testProduct = this.MockProductsRepository.FindByName("Silverlight Unleashed");

 

            Assert.IsNotNull(testProduct); // Test if null

            Assert.IsInstanceOfType(testProduct, typeof(Product)); // Test type

            Assert.AreEqual(3, testProduct.ProductId); // Verify it is the right product

        }

 

        /// <summary>

        /// Can we return all products?

        /// </summary>

        [TestMethod]

        public void CanReturnAllProducts()

        {

            // Try finding all products

            IList<Product> testProducts = this.MockProductsRepository.FindAll();

 

            Assert.IsNotNull(testProducts); // Test if null

            Assert.AreEqual(3, testProducts.Count); // Verify the correct Number

        }

 

        /// <summary>

        /// Can we insert a new product?

        /// </summary>

        [TestMethod]

        public void CanInsertProduct()

        {

            // Create a new product, not I do not supply an id

            Product newProduct = new Product

                { Name = "Pro C#", Description = "Short description here", Price = 39.99 };

 

            int productCount = this.MockProductsRepository.FindAll().Count;

            Assert.AreEqual(3, productCount); // Verify the expected Number pre-insert

 

            // try saving our new product

            this.MockProductsRepository.Save(newProduct);

 

            // demand a recount

            productCount = this.MockProductsRepository.FindAll().Count;

            Assert.AreEqual(4, productCount); // Verify the expected Number post-insert

 

            // verify that our new product has been saved

            Product testProduct = this.MockProductsRepository.FindByName("Pro C#");

            Assert.IsNotNull(testProduct); // Test if null

            Assert.IsInstanceOfType(testProduct, typeof(Product)); // Test type

            Assert.AreEqual(4, testProduct.ProductId); // Verify it has the expected productid

        }

 

        /// <summary>

        /// Can we update a prodict?

        /// </summary>

        [TestMethod]

        public void CanUpdateProduct()

        {

            // Find a product by id

            Product testProduct = this.MockProductsRepository.FindById(1);

 

            // Change one of its properties

            testProduct.Name = "C# 3.5 Unleashed";

 

            // Save our changes.

            this.MockProductsRepository.Save(testProduct);

 

            // Verify the change

            Assert.AreEqual("C# 3.5 Unleashed", this.MockProductsRepository.FindById(1).Name);

        }

    }

}

 

Download the Sample project and run the tests yourself:

MoqRepositorySample.zip (691.96 kb)


Categories: ASP.Net | C# | CodeProject | Moq | MVC | Unit Testing
Posted by Williarob on Tuesday, December 15, 2009 8:17 AM
Permalink | Comments (0) | Post RSSRSS comment feed

How to get the length (duration) of a media File in C# on Windows 7

If you have ever looked at a media file (audio or video) in the explorer window on a Windows 7 PC, you may have noticed that it displays additional information about that media file that previous versions of Windows didn't seem to have access to, for example the length/duration of a Quicktime Movie Clip:

 

Even right clicking the file and choosing Properties > Details does not give me this information on my Vista Ultimate PC. Of course, now that Windows has the ability to fetch this information, so do we as developers, through the Windows API (The DLL to Import by the way is "propsys.dll"):

        internal enum PROPDESC_RELATIVEDESCRIPTION_TYPE

        {

            PDRDT_GENERAL,

            PDRDT_DATE,

            PDRDT_SIZE,

            PDRDT_COUNT,

            PDRDT_REVISION,

            PDRDT_LENGTH,

            PDRDT_DURATION,

            PDRDT_SPEED,

            PDRDT_RATE,

            PDRDT_RATING,

            PDRDT_PRIORITY

        }

 

 

        [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true)]

        internal static extern int PSGetNameFromPropertyKey(

            ref PropertyKey propkey,

            [Out, MarshalAs(UnmanagedType.LPWStr)] out string ppszCanonicalName

        );

 

        [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true)]

        internal static extern HRESULT PSGetPropertyDescription(

            ref PropertyKey propkey,

            ref Guid riid,

            [Out, MarshalAs(UnmanagedType.Interface)] out IPropertyDescription ppv

        );

 

        [DllImport("propsys.dll", CharSet = CharSet.Unicode, SetLastError = true)]

        internal static extern int PSGetPropertyKeyFromName(

            [In, MarshalAs(UnmanagedType.LPWStr)] string pszCanonicalName,

            out PropertyKey propkey

        );

However, before you rush off to play with these, you may be interested to know that Microsoft has created a great Library that showcases this and many of the other new API features of Windows 7. It's called the WindowsAPICodePack and you can get it here.

If you open the WindowsAPICodePack Solution and compile the Shell Project, it creates a nice wrapper around all the neat new system properties available through propsys.dll. Adding a reference to WindowsAPICodePack.dll and WindowsAPICodePack.Shell.dll in a console application will allow you to get the duration of just about any media file that Windows recognizes. (Of course the more codec packs you install, the more types it will recognize, I recommend The Combined Community Codec Pack to maximize your range of playable files.)

Here is a simple example showing how to get the duration of a media file in C# using this library:

namespace ConsoleApplication1

{

    using System;

 

    using Microsoft.WindowsAPICodePack.Shell;

 

    class Program

    {

        static void Main(string[] args)

        {

            if(args.Length < 1)

            {

                Console.WriteLine("Usage: ConsoleApplication1.exe [Filename to test]");

                return;

            }

 

            string file = args[0];

            ShellFile so = ShellFile.FromFilePath(file);

            double nanoseconds;

            double.TryParse(so.Properties.System.Media.Duration.Value.ToString(), out nanoseconds);

            Console.WriteLine("NanaoSeconds: {0}", nanoseconds);

            if (nanoseconds > 0)

            {

                double seconds = Convert100NanosecondsToMilliseconds(nanoseconds) / 1000;

                Console.WriteLine(seconds.ToString());

            }

        }

 

        public static double Convert100NanosecondsToMilliseconds(double nanoseconds)

        {

            // One million nanoseconds in 1 millisecond, but we are passing in 100ns units...

            return nanoseconds * 0.0001;

        }

    }

}

As you can see, the System.Media.Duration Property returns a value in 100ns units so some simple math will turn it into seconds. Download the Test Project which includes the prebuilt WindowsAPICodePack.dll and WindowsAPICodePack.Shell.dll files in the bin folder:

ConsoleApplication1.zip (218.76 kb)

For the curious, I tested this on Windows XP and as you'd expect, it didn't work:

Unhandled Exception: System.DllNotFoundException: Unable to load DLL 'propsys.dll': The specified module could not be found. (Exception from HRESULT: 0x8007007E)

On Vista Ultimate SP2, it still didn't work - nanoseconds was always 0, though it didn't throw any exceptions.

For the older systems I guess we are limited to using the old MCI (Media Control Interface) API:

        using System.Runtime.InteropServices;

 

        [DllImport("winmm.dll")]

        public static extern int mciSendString(string lpstrCommand, StringBuilder lpstrReturnString, int uReturnLength, int hwndCallback);

 

        [DllImport("winmm.dll")]

        private static extern int mciGetErrorString(int l1, StringBuilder s1, int l2);

 

        private void FindLength(string file)

        {

            string cmd = "open " + file + " alias voice1";

            StringBuilder mssg = new StringBuilder(255);

            int h = mciSendString(cmd, null, 0, 0);

            int i = mciSendString("set voice1 time format ms", null, 0, 0);

            int j = mciSendString("status voice1 length", mssg, mssg.Capacity, 0);

            Console.WriteLine(mssg.ToString());

        }

Which works fine for .mp3 and .avi and other formats that play natively in Windows Media Player, but even with a codec pack installed, it doesn't work on Quicktime or .mp4 files, where the new Windows 7 API did.


Categories: C# | CodeProject | Windows | Windows 7
Posted by Williarob on Wednesday, October 21, 2009 12:14 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Using Expression Encoder 2 Silverlight 2 Templates in your project

Some time ago, I wrote a popular article on how to create a scrolling Silverlight 1.x Playlist using Microsoft Expression Encoder output. Well, I finally found some time to revisit that application to see how I might upgrade it to Silverlight 2. As you are probably aware, Expression Encoder 2 Service Pack 1 is now out and it ships with a handful of Player templates just for Silverlight 2. Among these new templates are two which already have built in Scrolling playlists and I thought I would test one out.

However, I already have all my videos encoded - including all the chapter point thumbnails, etc. so I didn't want to start inside Encoder, have it build my project and work from there like I did last time. This time, I created a new Silverlight 2.0 Usercontrol project in Visual Studio 2008 and worked for a week or two on the new look and feel for the site before I decided it was time to merge my project with the Expression Encoder template. I found Tim Heuer's blog entry on integrating these new templates very helpful, though I feel it is important to add that the template itself, meaning the look and feel of the player, is not stored in the dlls and it does not matter which of the template projects you open and compile, just referencing the dlls and following Tim's instructions will always give you the standard Silverlight 2 player. In order to add the look and feel you must copy (or merge) the contents of the Page.xaml file in that template into your own UserControl.

For my project I wanted the player to be a seperate UserControl, so I went to Project > New Item >  Silverlight User Control, and called it MediPlayer.xaml. Next I pasted everything from C:\Program Files\Microsoft Expression\Encoder 2\Templates\en\FrostedGallery\Source\Page.xaml into my new MediaPlayer.xaml file, then changed the x:Class at the very top to reflect my original NameSpace and Class Name. (If you forget what it was, just undo your paste, make a note of it and redo the paste).

I also wanted my Player control to be available on multiple pages, and I wanted it hidden most of the time, only to pop up in a pseudo modal 'Lightbox' format with a close button. To achieve this effect I simply used these techniques described by Scot Guthrie in his excellent tutorial on creating a Silverlight Digg application. So now I had my player, and I could show and hide it with the click of a button. Basically, my site showcases the James Bond Movies, and I have customized the Yet Another Carousel control so that you can click on the selected box art and read the synopsis, watch the Trailer and buy it on Amazon.com. My initial idea therefore, was to create a new PlayListItem programatically and add it to the player's PlayListCollection in the onLeftMouseButtonUp event of the button. What I found was that while this worked quite nicely, I ultimately ended up with a playlist slowly being generated by the user in the order in which the user clicked on the movies, making it harder to keep track of which item in the collection was which movie, but more importantly I couldn't figure out how to take full advantage of all the chapter information and thumbnails I had created for the original project. I could create chapter item objects, and add them to my own PlayListCollection object, but I could not bind that new object to my player since the Playlist Property is read only.

Reading more of Tim's blog I saw that you could add some xml to the InitParams of the control, but I have 24 videos, each with thumbnail paths for at least 4 chapter points and I didn't need to start typing that all into a single line to understand what a nightmare that would become: not only would it be hard to read and maintain but also it goes against the whole MVC seperation of code, data and presentation layers, ethic we have all grown so attached to.

More google searches led me to this solution which does allow you to move the creation of the parameters into the code behind, but seems to require tinkering with the original template code, which I am not opposed to, but I'd already thought of a cleaner solution. What I wanted was a way to create an xml file containing my entire playlist in this format:

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <playList>
   3:   <playListItems>
   4:     <playListItem title="Dr No" description="Trailer" mediaSource="ClientBin/01_dr_no.wmv" adaptiveStreaming="False" thumbSource="ClientBin/01_dr_no_Thumb.jpg" >
   5:       <chapters>
   6:         <chapter  position="29.176" thumbnailSource="ClientBin/01_dr_no_MarkerThumb 00.00.29.1760677.jpg" title="1" />
   7:         <chapter  position="49.374" thumbnailSource="ClientBin/01_dr_no_MarkerThumb 00.00.49.3748838.jpg" title="2" />
   8:         <!-- etc -->
  20:       </chapters>
  21:     </playListItem>
  22:   </playListItems>
  23: </playList>

and simply pass the file to my player. And my Solution turned out to be trivially easy: I created a new class that inherits from ExpressionMediaPlayer.MediaPlayer, and added a new method that would accept my file:

 

using System;

using System.Diagnostics;

using System.Net;

using System.Windows;

using System.Windows.Browser;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Ink;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

using System.Xml.Linq;

 

 

namespace Bond_Silverlight2

{

    public class MI6MediaPlayer : ExpressionMediaPlayer.MediaPlayer

    {

        public MI6MediaPlayer(): base()

        {

        }

 

        public void OnStartup(string xmlPlayList)

        {

            XDocument document = XDocument.Load(xmlPlayList);

            try

            {

                Playlist.Clear();

                Playlist.ParseXml(HtmlPage.Document.DocumentUri, document.ToString());

            }

            catch (System.Xml.XmlException xe)

            {

                Debug.WriteLine("XML Parsing Error:" + xe.ToString());

            }

            catch (NullReferenceException)

            {

            }

        }

 

    }

}

 


This required some minor changes to the MediaPlayer.xaml file, inorder to make it use my version of the player:

First of all, I replaced the <expression:ExpressionPlayer> tags with <Bond_Silverlight2:MI6MediaPlayer> tags and any static resource styles that had a target type of ExpressionPlayer:ExpressionPlayer also needed to be replaced, and then everything used my new player. Obviously, there was another step - how to initialize and pass my xml playlist to my new player. First, I created my xml file in the format outlined above. It is important to note, that my video files and associated JPEG files are stored on the webserver inside the Clientbin folder, rather than inside the xap file as resources or content. In the code behind of my MediaPlayer.xaml (that is the usercontrol I pasted the page.xaml into earlier, not the MI6MediaPlayer class from the code above), I call the startup method using a link to my xml file:

 

 

using System;

using System.Collections.Generic;

using System.Linq;

using System.Net;

using System.Windows;

using System.Windows.Controls;

using System.Windows.Documents;

using System.Windows.Input;

using System.Windows.Media;

using System.Windows.Media.Animation;

using System.Windows.Shapes;

 

namespace Bond_Silverlight2

{

    public partial class MediaPlayer : UserControl

    {

        public MediaPlayer()

        {

            InitializeComponent();

            this.Loaded += new RoutedEventHandler(MediaPlayer_Loaded);

        }

 

        void MediaPlayer_Loaded(object sender, RoutedEventArgs e)

        {

            Player1.OnStartup("Playlist.xml");

        }

 

        private void Button_Click(object sender, RoutedEventArgs e)

        {

            Player1.Stop();

            Visibility = System.Windows.Visibility.Collapsed;

        }

    }

}

 

The xml file ("Playlist.xml") is stored as Content within the xap file. This should be the default behavior if you created the xml file inside Visual Studio by using the Project > Add Item menu, but if you didn't, you can check by right clicking on the xml file, choosing Properties and checking that the Build Action is "Content", and Copy to Output Directory is set to "Do Not Copy".

Now when my Player is first loaded into memory, my full playlist is immediately available.

 


Posted by Williarob on Wednesday, April 08, 2009 1:47 PM
Permalink | Comments (1) | Post RSSRSS comment feed

Multi-Threading in ASP.NET

ASP.Net Threading

Inside the ASP.Net Worker Process there are two thread pools. The worker thread pool handles all incoming requests and the I/O Thread pool handles the I/O (accessing the file system, web services and databases, etc.). Each App Domain has its own thread pool and the number of operations that can be queued to the thread pool is limited only by available memory; however, the thread pool limits the number of threads that can be active in the process simultaneously.

  Asp.Net Threading, Threadpools
  Source: Microsoft Tech Ed 2007 DVD: Web 405  "Building Highly Scalable ASP.NET Web Sites by Exploiting Asynchronous Programming Models" by Jeff Prosise.

So how many threads are there in these thread pools? I had always assumed that the number of threads varies from machine to machine – that ASP.NET and IIS were carefully and cleverly balancing the number of available threads against available hardware, but that is simply not the case. The fact is that ASP.Net installs with a fixed, default number of threads to play with: the 1.x Framework defaults to just 20 worker threads (per CPU) and 20 I/O threads (per CPU). The 2.0 Framework defaults to 100 threads in each pool, per CPU. Now this can be increased by adding some new settings to the machine.config file. The default worker thread limit was raised to 250 per CPU and 1000 I/O threads per CPU with the .NET 2.0 SP1 and later Frameworks. 32 bit windows can handle about 1400 concurrent threads, 64 bit windows can handle more, though I don’t have the figures.

In a normal (synchronous) Page Request a single worker thread handles the entire request from the moment it is received until the completed page is returned to the browser. When the I/O operation begins, a thread is pulled from the I/O thread pool, but the worker thread is idle until that I/O thread returns. So, if your page load event fires off one or more I/O operations, then that main worker thread could be idle for 1 or more seconds and in that time it could have serviced hundreds of additional incoming page requests.

  Asp.net Threadpool Saturation
  Source: Microsoft Tech Ed 2007 DVD: Web 405  "Building Highly Scalable ASP.NET Web Sites by Exploiting Asynchronous Programming Models" by Jeff Prosise.

So long as the number of concurrent requests does not exceed the number of threads available in the pool, all is well. But when you are building enterprise level applications the thread pool can become depleted under heavy load, and remember by default heavy load is more than just 200 simultaneous requests assuming a dual CPU Server.

When this happens, new requests are entered into the request queue (and the users making the requests watch that little hour glass spin and consider trying another site). ASP.NET will allow the request queue to grow only so big before it starts to reject requests at which point it starts returning Error 503, Service Unavailable.

If you are not aware of this “Glass Ceiling of Scalability”, this is a perplexing error – one that never happened in testing and may not be reproducible in your test environment, as it only happens under extreme load.

Asynchronous Programming models in ASP.NET

To solve this problem ASP.Net provides four asynchronous Programming models. Asyncronous Pages, Asyncronous HttpHandlers, Asyncronous HttpModules and Asyncronous Web Services. The only one that is well documented and reasonably well known is the asynchronous Web Services model. Since there is quite a lot of documentation on that, and since in future web services should be implemented using the Windows Communication Foundation, we shall concentrate only on the other three.

Let’s begin with the first asynchronous programming model, Asynchronous Pages.

Asynchronous Pages

  Lifecycle of Synchronous/Asynchronous Pages in ASP.NET
  Source: Microsoft Tech Ed 2007 DVD: Web 405  "Building Highly Scalable ASP.NET Web Sites by Exploiting Asynchronous Programming Models" by Jeff Prosise.

To make a page Asynchronous, we insert what we refer to as an “Async Point” into that page’s lifecycle, which you can see in green on the right. We need to write and register with ASP.NET a pair of Begin and End Events. At the appropriate point in the page’s lifecycle, ASP.NET will call our begin method. In the begin method we will launch an asynchronous I/O operation, for example an asynchronous database query, and we will immediately return from the begin method. As soon as we return, ASP.Net will drop the thread that was assigned to that request back into the thread pool where it may service hundreds or even thousands of additional page requests while we wait for our I/O operation to complete.

As you’ll see when we get to the sample code, we return from our begin method an IAsyncResult Interface, through which we can signal ASP.NET when the async operation that we launched has completed. It is when we do that, that ASP.NET reaches back into the thread pool, pulls out a second worker thread and calls our end method, and then allows the processing of that request to resume as normal.

So, from ASP.NET’s standpoint it is just a normal request, but it is processed by 2 different threads; and that will bring up a few issues that we’ll need to discuss in a few moments.

Now, none of this was impossible with the 1.1 framework, but it was a lot of extra work, and you lost some of the features of ASP.NET in the process. The beauty of the 2.0 and later frameworks is that this functionality is built right into the Http pipeline, and so for the most part everything works in the asynchronous page just as it did in the synchronous one.

In order to create an Asynchronous page you need to include the Async=”True” attribute in the page directive of your .aspx file. That directive tells the ASP.NET engine to implement an additional Interface on the derived page class which lets ASP.NET know at runtime that this is an asynchronous page.

What happens if you forget to set that attribute? Well the good news is that the code will still run just fine, but it will run synchronously, meaning that you did all that extra coding for nothing. I should also point out that to make an Asynchronous data call, you also need to add “async=true;” or “Asynchronous Processing=true;” to your connection string – If you forget that and make your data call asynchronously, you will get a SQL Exception.

The second thing we need to do in order to create an asynchronous page is to register Begin and End Events. There are 2 ways to register these events. The first way is to use a new method introduced in ASP.NET 2.0 called AddOnPreRenderCompleteAsync:

using System;

using System.Net;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

 

public partial class temp : System.Web.UI.Page

{

    private static readonly Uri c_UrlImage1 = new Uri(@"http://www.asyncassassin.com/asyncassassin/image.axd?picture=2008%2f12%2fSlide3.JPG");

    private HttpWebRequest request;

 

    void Page_Load(object sender, EventArgs e)

    {

        request = (HttpWebRequest)WebRequest.Create(c_UrlImage1);

 

        AddOnPreRenderCompleteAsync(

            BeginAsyncOperation,

            EndAsyncOperation

        );

    }

 

    IAsyncResult BeginAsyncOperation(object sender, EventArgs e,

        AsyncCallback cb, object state)

    {

        // Begin async operation and return IAsyncResult

        return request.BeginGetResponse(cb, state);

    }

 

    void EndAsyncOperation(IAsyncResult ar)

    {

        // Get results of async operation

        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(ar);

        Label1.Text = String.Format("Image at {0} is {1:N0} bytes", response.ResponseUri, response.ContentLength);

    }

}

 


The second way is to use RegisterAsyncTask:

using System;

using System.Net;

using System.Web;

using System.Web.UI;

using System.Web.UI.WebControls;

 

public partial class temp : System.Web.UI.Page

{

    private static readonly Uri c_UrlImage1 = new Uri(@"http://www.asyncassassin.com/asyncassassin/image.axd?picture=2008%2f12%2fSlide3.JPG");

    private HttpWebRequest request;

 

    void Page_Load(object sender, EventArgs e)

    {

        request = (HttpWebRequest)WebRequest.Create(c_UrlImage1);

 

        PageAsyncTask task = new PageAsyncTask(

                BeginAsyncOperation,

                EndAsyncOperation,

                TimeoutAsyncOperation,

                null

            );

 

        RegisterAsyncTask(task);

    }

 

    IAsyncResult BeginAsyncOperation(object sender, EventArgs e,

        AsyncCallback cb, object state)

    {

        // Begin async operation and return IAsyncResult

        return request.BeginGetResponse(cb, state);

    }

 

    void EndAsyncOperation(IAsyncResult ar)

    {

        // Get results of async operation

        HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(ar);

        Label1.Text = String.Format("Image at {0} is {1:N0} bytes", response.ResponseUri, response.ContentLength);

    }

 

    void TimeoutAsyncOperation(IAsyncResult ar)

    {

        // Called if async operation times out (@ Page AsyncTimeout)

        Label1.Text = "Data temporarily unavailable";

    }   

}

 

These methods can be called anywhere in the page’s lifecycle before the PreRender event, and are typically called from the Page_Load event or from the click event of a button during a postback. By the way, you can register these methods from within a UserControl, as long as that control is running on a page that has set the async = true attribute. Again, if it runs on a page without that attribute, the code will still run just fine, but it will run synchronously.

As you can see from just these simple examples, building asynchronous pages is more difficult than building synchronous ones. I’m not going to lie to you. And real world use of these techniques is even more complicated – there is no Business Logic or data layer in the examples above. I don’t want you to leave here believing that you need to make every page asynchronous. You don’t. What I recommend, is doing surgical strikes. Identify that handful of pages in your application that perform the lengthiest I/O and consider converting those into asynchronous pages. The cool thing about this, is that it can improve not only scalability, but also performance, because when you are not holding onto the threads, new requests get into the pipeline faster, they spend less time waiting in that application request queue out there. So, users are happier because pages that they would have had to wait on before – even the ones you have not converted to asynchronous pages, but which might have been delayed while threads were idle, will now load faster. What’s more, as you’ll see in a moment, using RegisterAsyncTask will allow you to perform I/O operations in parallel, which may also improve performance. Having said that, making pages asynchronous is not really about improving performance, it is about improving scalability – making sure that we use the threads in the thread pool as efficiently as we possibly can.

Now I’m sure you are wondering why there are two ways, what the differences are between them, and when you should choose one over the other. Well, there are 3 important differences between AddOnPreRenderCompleteAsync and RegisterAsyncTask.

  1. As we have seen, RegisterAsyncTask allows you to specify a timeout method. It is important to note that the timeout value you specify in the Page Directive of your .aspx page <%@ Page Async="true" AsyncTimeout="5" ... %> is the timeout for ALL asynchronous tasks the page is performing, not 5 secs per async task - all async tasks must be competed within 5 seconds, so be sure to allow enough time here.
  2. If you use AddOnPreRenderCompleteAsync, you may find that some things that worked before, no longer work. For example, if you are using User.Identity.Name in your code to get the authenticated username inorder to personalize a page. If this method is called by the first thread it will work fine. But if you call it on the second thread – in your End method or any of the events that fire after the end method User.Identity.Name will be null. This is because as a Request travels through the ASP.NET Http Pipeline, it is accompanied by an object of type HttpContext that basically encapsulates all of the information that ASP.NET knows about that request. When you use AddOnPreRenderCompleteAsync ASP.NET does not take the extra time to map everything in that context object from thread one to thread two. That’s why User.Identity.Name does not work in thread two. In fact, you will often find that HttpContext.Current is null in thread two. However, if you use RegisterAsyncTask, ASP.Net DOES map everything in that context from thread one to thread two. It does take a few microseconds longer to do this, but it will make your life considerably easier.
  3. The third difference is probably the most important of all. AddOnPreRenderCompleteAsync is a quick and easy way of making a page asynchronous and works well if you have a simple page that needs to perform only 1 asynchronous I/O operation. In real life, a page often needs to perform multiple database queries, or grab data from a webservice and pass it to a database, or something like that. The cool thing about RegisterAsyncTask is that it allows you to quickly and easily queue up multiple Async I/O operations. The last argument is a Boolean value that allows you to specify whether each task can run in parallel. Some times you need to wait for one data call to complete in order to send that data somewhere else, but other times you may need to get data from multiple, unrelated sources and this allows you to fetch them all at the same time, instead of one after the other.

  Controlling Order of Operations
  Source: Microsoft Tech Ed 2007 DVD: Web 405  "Building Highly Scalable ASP.NET Web Sites by Exploiting Asynchronous Programming Models" by Jeff Prosise.

N-Tier Applications

OK. So I expect some of you are thinking “But what if I have a data access layer in my application? My pages can’t go directly to the database, they have to go through that data access layer, or they have to go through my BLL, which calls the Data Access Layer.”

Well, ideally, you should simply add the asynchronous methods to your DAL. If you wrote the DAL yourself or have access to its source code, you should add the Begin and End methods to it Adding the asynchronous methods to your DAL is the best, most scalable solution and doesn’t change the example code much at all: Instead of calling begin and end methods defined inside the page class, you simply call MyDAL.Begin… or MyBll.Begin… when you call RegisterAsyncTask or AddOnPreRenderAsync.

Unfortunately, neither Llblgen nor the Enterprise library (nor LINQ for that matter) supports asynchronous data calls natively. However, I believe that you can modify the generated code in llblgen to enable asynchronous data calls. You could also crack open the source code of the Enterprise library and add the asynchronous methods yourself, but before you try check to see if it has already been done.

Asynchronous HTTP Handlers

The 2nd Asynchronous Programming model in ASP.NET is for HttpHandlers and has been around since .Net 1.x, but was not documented any better in version 2 than it was in version 1. Http Handlers are one of the two fundamental building blocks of ASP.NET, an http handler is an object that is built to handle http requests and convert them into http responses. For the most part, each handler corresponds to a file type. For example, there is a built in handler in ASP.NET that handles .aspx files. It is that handler that knows how to instantiate a control tree and send that tree to a rendering engine. The ASMX Handler knows how to decode SOAP and allows us to build web services.

Basically an HTTP Handler is just a class that implements the IHttpHandler interface, which consists of an IsResuable Boolean function and a ProcessRequest method which is the heart of an httphandler as its job is to turn a request into a response. The ProcessRequest method is passed an HttpContext Object containing all the data asp.net has collected about the request, as well as exposing the Session, Server, Request and Response objects that you are used to working with in page requests.

 

using System.Web;

 

public class HelloHandler : IHttpHandler

{

    public void ProcessRequest(HttpContext context)

    {

        string name = context.Request["Name"];

        context.Response.Write("Hello, " + name);

    }

 

    public bool IsReusable

    {

        get { return true; }

    }

}

 

There are 2 ways to build them. One way is to add the class to your project and register is in the web.config. If you want to register it for any file extension not currently handled by asp.net, you would need to add that extension to IIS. The easier way, is to deploy them as .ASHX files. The ASHX extension has already been registered in IIS, it is auto compiled, no changes are required in the web.config and performance is the same. Ok. So you know what they are and how to build one, when is an appropriate time to use one?

Handlers are commonly used to generate custom XML and RSS feeds, to unzip and render files stored as BLOB fields in the database including image files or logos, HTTP Handlers can also be used as the target of AJAX calls.

A common mistake that programmers new to .net, especially those like myself who came from classic ASP or PHP, is to use the Page_Load event of a page to create a new http response. For example, before I learned about httphandlers, I would use the page load event to create an xml document or a dynamic PDF file and output it to the response stream with a response.End() to prevent the page continuing after I output my file. The problem with that approach is that you are executing a ton of code in asp.net that doesn’t need to execute. When ASP.NET sees that request come in, it thinks it is going to need to build and render a control tree. By pointing the link at the handler instead, you will gain a 10-20% performance increase every time that request is fetched, just because of the overhead you have reduced. Put simply, Http Handlers minimize the amount of code that executes in ASP.NET.

To implement an Asynchronous handler you use the interface IHttpAsyncHandler, which adds BeginProcessRequest and EndProcessRequest methods. The threading works the same way as with an async page. After the begin method is called, the thread returns to the thread pool and handles other incoming requests until the I/O thread completes its work, at which point it grabs a new thread from the thread pool and completes the request.

Page.RegisterAsyncTask cannot be used here, so if you need to run multiple async tasks you will need to implement your own IAsyncResult Interface and pass in your own callbacks to prevent the EndProcessRequest method being called before you have completed all your async operations.

Asynchronous HTTP Modules

HTTP Modules are another fundamental building block of ASP.NET. They don’t handle requests, instead they sit in the HTTP Pipeline where they have the power to review every request coming in and every response going out. Not only can they view them, but they can modify them as well. Many of the features of ASP.NET are implemented using httpmodules: authentication, Session State and Caching for example, and by creating your own HTTP Modules you can extend ASP.NET in a lot of interesting ways. You could use an HTTP Module for example to add google analytics code to all pages, or a custom footer. Logging is another common use of HTTP Modules.

E-Commerce web sites can take advantage of HTTP Modules by overriding the default behavior of the Session Cookie. By default, ASP.NET Session Cookies are only temporary, so if you use them to store shopping cart information, after 20 minutes of inactivity, or a browser shut down they are gone. You may have noticed that Amazon.com retains shopping cart information much longer: You could shut down your laptop, fly to Japan and when you restart and return to Amazon your items will still be there. If you wanted to do this in ASP.NET you could waste a lot of time writing your own Session State Cookie Class, or you could write about 10 lines of code in the form of an HTTP Module that would intercept the cookie created by the Session Object before it gets to the browser, and modify it to make it a persistent cookie. So, there are lots and lots of practical uses for HTTP Modules.

An Http Module is nothing more than a class that implements the IHttpModule Interface, which involves an Init method for registering any and all events that you are interested in intercepting, and a dispose method for cleaning up any resources you may have used.

 

using System;

using System.Web;

 

public class BigBrotherModule : IHttpModule

{

    public void Init(HttpApplication application)

    {

        application.EndRequest +=

            new EventHandler(OnEndRequest);

    }

 

    void OnEndRequest(Object sender, EventArgs e)

    {

        HttpApplication application = (HttpApplication)sender;

        application.Context.Response.Write

            ("Bill Gates is watching you");

    }

 

    public void Dispose() { }

}

 

The events you can intercept in an HTTP Module:

 

  HttpApplication Events 
  Source: Microsoft Tech Ed 2007 DVD: Web 405  "Building Highly Scalable ASP.NET Web Sites by Exploiting Asynchronous Programming Models" by Jeff Prosise.

 Notice the HTTP Handler at the end there that converts the request into a response. These events will always fire in this order, in every request. The Authenticate Request event is the one fired by ASP.NET when a requested page requires authentication. It checks to see if you have an authentication cookie and if you do not, redirects the request to the login page. In the simple example, I was using that End Request event, which is the last one before the response is sent to the browser.

So, that is what HTTP Modules are for, and how they work. Why do we need an Asynchronous version? Well if you really want to see how scalable your application is, add an HTTP Module that makes a synchronous call to a webservice or a database. Since the event you register will be fired on every request, you will tie up an additional thread from the asp.net thread pool on every single request that is just waiting for these I/O processes to complete. So, if you write a synchronous HTTP Module that inserts a record into a database for every single request, and that insert takes 1 second, EVERY single request handled by your application will be delayed by 1 second. So if you need to do any type of I/O from within an HTTP Module, I recommend you make the calls asynchronously and if you are retrieving data, cache it!

To Register Async Event Handlers in an http module - In the Init Method, simply register your begin and end methods using AddOnPreRequestHandlerExecuteAsync:

 

using System.Web;

 

public void Init (HttpApplication application)

{

    AddOnPreRequestHandlerExecuteAsync (

        new BeginEventHandler (BeginPreRequestHandlerExecute),

        new EndEventHandler (EndPreRequestHandlerExecute)

    );

}

 

IAsyncResult BeginPreRequestHandlerExecute (Object source,

    EventArgs e, AsyncCallback cb, Object state)

{

    // TODO: Begin async operation and return IAsyncResult

}

 

void EndPreRequestHandlerExecute (IAsyncResult ar)

{

    // TODO: Get results of async operation

}

Error Handling while multithreading

Errors can happen at any point during the execution of a command. When ASP.NET can detect errors before initiating the actual async operation, it will throw an exception from the begin method; this is very similar to the synchronous case in which you get the exceptions from a call to ExecuteReader or similar methods directly. This includes invalid parameters, bad state of related objects (no connection set for a SqlCommand, for example), or some connectivity issues (the server or the network is down, for example).

Now, once we send the operation to the server and return, ASP.NET doesn’t have any way to let you know if something goes wrong at the exact moment it happens. It cannot just throw an exception as there is no user code above it in the stack when doing intermediate processing, so you wouldn't be able to catch an exception if it threw one. What happens instead is that ASP.Net stores the error information, and signals that the operation is complete. Later on, when your code calls the end method, ASP.Net detects that there was an error during processing and the exception is thrown.

The bottom line is that you need to be prepared to handle errors in both the begin and the end methods, so it is wise to wrap both events in a try – Catch block.

Conclusion

Now you have seen three of the asynchronous programming models ASP.NET has to offer, hopefully I have impressed upon you how important it is to at least consider using them when creating pages that do I/O if you expect those pages to be heavily trafficked. Remember you can also create asynchronous web services. I didn’t cover those here because there is pretty good documentation for that already.

The good thing about Asynchronous Programming models is that it enables us to build scalable and responsive applications that use minimal resources (threads/context switches).

What is the down side? Well it forces you to split the code into many callback methods, making it hard to read, confusing to debug and difficult for programmers unfamiliar with asynchronous programming to maintain.

With this in mind, whenever I add an asynchronous method to an object in the my projects, I also add a traditional Synchronous version. For example, if I had created a BeginUpdatexxx() Method in the BLL, there would also be a traditional Updatexxx() Method, so that if anyone else finds themselves having to use that object, they won’t be left scratching their heads, wondering “how on earth do I use that?”

Asynchronous command execution is a powerful extension to.NET. It enables new high-scalability scenarios at the cost of some extra complexity.

For more information on multi-threading in ASP.NET I highly recommend you read "Custom Threading in ASP.Net".


Posted by Williarob on Tuesday, December 16, 2008 5:30 PM
Permalink | Comments (0) | Post RSSRSS comment feed

Add Asynchronous Data Methods to the Enterprise Library

A Data Access Layer that does not support Asychronous I/O is not scalable. Period. However, since Microsoft kindly provided the source code along with the Enterprise Library, it is possible to make the Enterprise Library's Data Application Block scalable by adding the Asynchronous methods (BeginExecuteNonQuery, BeginExecuteReader, etc.) to the SQL Database Object. I took the code from the 3.x library and modified the file SqlDatabase.cs found here c:\EntLib3Src\App Blocks\Src\Data\Sql (assuming you installed the source files to the root of your C drive). The following download includes the modified binaries and my SqlDatabase.cs code file:

AsyncEL.zip (348.39 kb)

I don't think I implemented every overload, and again this is for the 3.0 library, but you should be able to modify the 4.x libraries and add more overloads by following the patterns I'm using. To use the binaries included in the zip file above, simply drop them into the bin folder of your web site or web application project, add references if necessary and don't forget to add the async=true attribute to both your <% page %>  tag and your connection string! Here is a simple example of how you might use it, to get you started:

 

    1 using System;

    2 using System.Collections;

    3 using System.Configuration;

    4 using System.Data;

    5 using System.Data.SqlClient;

    6 using System.Linq;

    7 using System.Web;

    8 using System.Web.Configuration;

    9 using System.Web.Security;

   10 using System.Web.UI;

   11 using System.Web.UI.HtmlControls;

   12 using System.Web.UI.WebControls;

   13 using System.Web.UI.WebControls.WebParts;

   14 using System.Xml.Linq;

   15 

   16 public partial class temp : System.Web.UI.Page

   17 {

   18     protected void Page_Load(object sender, EventArgs e)

   19     {

   20         if (!IsPostBack)

   21         {

   22             RegisterAsyncTask(new PageAsyncTask(new BeginEventHandler(BeginUpdateByAsyncEL), new EndEventHandler(EndUpdateByAsyncEL), new EndEventHandler(AsyncUpdateTimeout), null, true));

   23         }

   24     }

   25 

   26     /// <summary>

   27     /// Creates a SqlDatabase Object and stores it in HttpContext for the duration of the request.

   28     /// </summary>

   29     /// <value></value>

   30     /// <returns>A SqlDatabase Object</returns>

   31     /// <remarks></remarks>

   32     public static Microsoft.Practices.EnterpriseLibrary.Data.Sql.SqlDatabase AsyncNorthWindDB

   33     {

   34         get

   35         {

   36             if (HttpContext.Current == null)

   37             {

   38                 return new Microsoft.Practices.EnterpriseLibrary.Data.Sql.SqlDatabase(WebConfigurationManager.ConnectionStrings["AsyncNorthwind"].ConnectionString);

   39             }

   40             if (HttpContext.Current.Items["AsyncNorthWindDB"] == null)

   41             {

   42                 HttpContext.Current.Items.Add("AsyncNorthWindDB", new Microsoft.Practices.EnterpriseLibrary.Data.Sql.SqlDatabase(WebConfigurationManager.ConnectionStrings["AsyncNorthwind"].ConnectionString));

   43             }

   44             return HttpContext.Current.Items["AsyncNorthWindDB"];

   45         }

   46     }

   47 

   48     /// <summary>

   49     /// Begins an asynchronous call the the Northwind Database 

   50     /// </summary>

   51     /// <param name="sender"></param>

   52     /// <param name="e"></param>

   53     /// <param name="cb"></param>

   54     /// <param name="state"></param>

   55     /// <returns>An IAsyncResult Interface</returns>

   56     /// <remarks>These methods do not exist in the standard Enterprise Library</remarks>

   57     public IAsyncResult BeginUpdateByAsyncEL(object sender, EventArgs e, AsyncCallback cb, object state)

   58     {

   59         SqlCommand cmd = new SqlCommand("Update Products SET UnitPrice = 79.0 WHERE productID = 20");

   60         return AsyncNorthWindDB.BeginExecuteNonQuery(cmd, cb, state);

   61     }

   62 

   63     public void EndUpdateByAsyncEL(IAsyncResult ar)

   64     {

   65         AsyncNorthWindDB.EndExecuteNonQuery(ar);

   66     }

   67 

   68     /// <summary>

   69     /// Async Timeout method

   70     /// </summary>

   71     /// <param name="ar"></param>

   72     /// <remarks></remarks>

   73     public void AsyncUpdateTimeout(IAsyncResult ar)

   74     {

   75         Label1.Text = "Connection Timeout";

   76     }

   77 }

 

Download the Full Enterprise Library (including the complete source code) from http://www.codeplex.com/entlib.

 

kick it on DotNetKicks.com


Posted by Williarob on Monday, December 01, 2008 8:50 AM
Permalink | Comments (0) | Post RSSRSS comment feed

Sample Scrolling Silverlight Video Playlist

 A real world example of this technique can be seen at http://www.thejamesbondmovies.com

TAKE ME TO THE VERSION FOR SILVERLIGHT 2

It only took me about an hour to create this example. I started by dragging all 8 videos into Microsoft Expression Encoder, selected the "Expression" player template and clicked encode. This gave me the full player functionality you see here, play, stop, pause, etc. and by default it simply played all 8 videos one after the other. But I wanted my users to be able to pick and chose which videos to play, so I opened the project in Microsoft Expression Blend 2 September preview and resized the outer, root canvas by setting the height to 593 to give me room to place the thumbnails, and gave it a black background color.

The XAML 

Next I created the arrows that move the playlist left and right. If you have arrows as PNG images you can use them, but I chose to create them using XAML by drawing a white square, filling it with white color converting it to a path, and then using the pen tool to delete a corner and thereby converting it to a triangle which I simply rotated, moved and sized until it looked right. Set the cursor property to "Hand" so that users know it is a button. I then copied it, rotated it to point the other way and moved the second arrow into position on the other side. This gave me the XAML below which appeared just before the closing </canvas> tag.

<!-- Playlist region starts here -->
<!-- Navigation Arrows -->
<Path x:Name="LeftArrow" Opacity="0.74" Width="38" Height="38" Stretch="Fill" Stroke="#FF000000" Canvas.Left="11" Canvas.Top="514" Data="M37.5,0.5 L37.5,37.5 0.5,37.5 z" Fill="#FFFFFFFF" Cursor="Hand" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="134.119"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Path.RenderTransform>
</Path>
<Path x:Name="RightArrow" Opacity="0.74" Width="38" Height="38" Stretch="Fill" Stroke="#FF000000" Canvas.Left="588" Canvas.Top="514" Data="M37.5,0.5 L37.5,37.5 0.5,37.5 z" Fill="#FFFFFFFF" Cursor="Hand" RenderTransformOrigin="0.5,0.5">
<Path.RenderTransform>
<TransformGroup>
<ScaleTransform ScaleX="1" ScaleY="1"/>
<SkewTransform AngleX="0" AngleY="0"/>
<RotateTransform Angle="314.365"/>
<TranslateTransform X="0" Y="0"/>
</TransformGroup>
</Path.RenderTransform>
</Path>

Next I dragged the first thumbnail (for movie # 1) onto the area, roughly where you see it now, resized it and moved it into position. I named it "play0", set its opacity to 74% to make an easy rollover effect that you'll see later on; then went into the XAML editor and simply copied the Image element 3 more times, renaming each one in turn to play1, play2, etc. and updating the Source property of each to point to the right thumbnail image. Next I had to move the new images, since they all sat on top of one another, using the basic formula left=previousImageLeft + Image Width + 4 pixels. Then playing with the spacing between them until it was roughly even. Given the width of my thumbnail and the width of the canvas it turned out to be impossible without further resizing and in the end I decided that for this example it was good enough. So I now had my four thumbnail "buttons" making up my playlist and if you can fit all of your items on the screen you can skip ahead to the JavaScript, but if you want to have them scroll keep reading.

I grouped these four images into a canvas - have Blend do this for you by control + clicking on the object names (in this case play0, play1, etc.) in the Objects and Timeline area and then pressing ctl + G or right clicking and choosing Group Into > Canvas. It is better to have blend create the canvas for you as it will update all the canvas.left, canvas.top properties for you. I called this new canvas "playlist1", then using the XAML editor copied it and created "playlist2", naming the objects play4, play5, etc. and updating the Source of each as before. Then I moved this canvas off the screen by simply setting the canvas.Left Property to a value I knew would push it out of sight. Finally, I grouped playlist1 and playlist2 into another canvas, this time calling it "Library".

Using the timeline editor I created a simple animation that moved the "Library" Canvas 613 pixels to the left over the course of 2 seconds. If you have never done this before it is really easy:

The video content presented here requires JavaScript to be enabled and the latest version of the Macromedia Flash Player. If you are you using a browser with JavaScript disabled please enable it now. Otherwise, please update your version of the free Flash Player by downloading here.

As you can hopefully see on the video, you simply click on "Open, create or manage Storyboards", click "Create new", give it a name, make sure "Create as Resource" is checked so that we can access it through code, move 2 seconds into the timeline and add a keyframe to start the recorder, then make your changes. In this case we are changing the Left poperty so that it moves 613 pixels to the left - just far enough to bring the second "page" of buttons onto the screen. Stop the recorder and as you scrub through the timeline you can see the animation or click the play button to preview it.

Making it animate back the other way was a little trickier to do using the IDE, so I simply copied the XAML and changed the values myself.

Now if you have been trying this yourself you might be wondering why your thumbnails can be seen moving underneath, or even on top of the left and right arrows created earlier, while mine do not. The answer is that I have put my "Library" Canvas inside another Canvas called "ClippedCanvas" which has been "clipped", or cropped if you prefer using RectangleGeometry. Everything that falls outside the geometry you provide is hidden, or "clipped." The numbers represent X coordinates, Y coordinates, Width & Height in that order. X & Y in this case are relative to the container canvas ("ClippedCanvas"). So basically I am cropping an area from the top left of where Clipped Canvas begins, 550 pixels wide and 114 high, anything within the canvas that falls outside that region will not be seen. If you click on "ClippedCanvas" in the Objects and Timeline you will see it outlined in Blend and have a better understanding of where it is drawn.

So, the Final XAML for my Playlist region looks like this:

<!-- The outer canvas here is clipped: only the area defined by the rectangle geometry is visible  -->
<!-- This is necessary as when we animate the 'Library' canvas inside it we do not want to see the thumbnails slide under the navigation arrows and off the screen-->
<Canvas x:Name="ClippedCanvas" Canvas.Top="491" Canvas.Left="43" Width="550" Height="90">
<Canvas.Clip>
<RectangleGeometry Rect="0, 0, 550, 114"/>
</Canvas.Clip>
<!-- Animations to move the playlist left and right. They are numbered so that we can call them logically from code -->
<Canvas.Resources>
<Storyboard x:Name="MoveLeft01">
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="Library" From="13" To="-613" Duration="0:0:2" />
</Storyboard>
<Storyboard x:Name="MoveRight02">
<DoubleAnimation Storyboard.TargetProperty="(Canvas.Left)" Storyboard.TargetName="Library" From="-613" To="13" Duration="0:0:2" />
</Storyboard>
</Canvas.Resources>
<!-- The Library Canvas groups the playlist buttons into a single element that can be easily animated left - right. -->
<Canvas Width="1157.275" Height="82.96" Canvas.Left="0" Canvas.Top="0" x:Name="Library">
<Canvas Width="550.275" Height="82.96" x:Name="playlist1">
<Image x:Name="play0" Opacity="0.74" Width="133.275" Height="82.96" Source="1.png" Stretch="Fill" Cursor="Hand" />
<Image x:Name="play1" Opacity="0.74" Width="133.275" Height="82.96" Source="2.png" Stretch="Fill" Cursor="Hand" Canvas.Left="139"/>
<Image x:Name="play2" Opacity="0.74" Width="133.275" Height="82.96" Source="3.png" Stretch="Fill" Cursor="Hand" Canvas.Left="278"/>
<Image x:Name="play3" Opacity="0.74" Width="133.275" Height="82.96" Source="4.png" Stretch="Fill" Cursor="Hand" Canvas.Left="417"/>
</Canvas>
<Canvas Width="550.275" Height="82.96" x:Name="playlist2" Canvas.Left="607">
<Image x:Name="play4" Opacity="0.74" Width="133.275" Height="82.96" Source="5.png" Stretch="Fill" Cursor="Hand" />
<Image x:Name="play5" Opacity="0.74" Width="133.275" Height="82.96" Source="6.png" Stretch="Fill" Cursor="Hand" Canvas.Left="139"/>
<Image x:Name="play6" Opacity="0.74" Width="133.275" Height="82.96" Source="7.png" Stretch="Fill" Cursor="Hand" Canvas.Left="278"/>
<Image x:Name="play7" Opacity="0.74" Width="133.275" Height="82.96" Source="8.png" Stretch="Fill" Cursor="Hand" Canvas.Left="417"/>
</Canvas>
</Canvas>
</Canvas>

The JavaScript

Code that I added or changed is in bold, the rest is straight from the Encoder's original output.

var curPos = 1; //track the current position of the playlists
var maxPos = 2; //How many pages of clips do we have?
var cVideos = 8; //How many video Clips do we have?
function get_mediainfo(mediainfoIndex) {
switch (mediainfoIndex) {
case 0:
return { "mediaUrl": "Movie1.wmv",
"placeholderImage": "",
"chapters": [
] };
case 1:
return { "mediaUrl": "Movie2.wmv",
"placeholderImage": "",
"chapters": [
] };
case 2:
return { "mediaUrl": "Movie3.wmv",
"placeholderImage": "",
"chapters": [
] };
case 3:
return { "mediaUrl": "Movie4.wmv",
"placeholderImage": "",
"chapters": [
] };
case 4:
return { "mediaUrl": "Movie5.wmv",
"placeholderImage": "",
"chapters": [
] };
case 5:
return { "mediaUrl": "Movie6.wmv",
"placeholderImage": "",
"chapters": [
] };
case 6:
return { "mediaUrl": "Movie7.wmv",
"placeholderImage": "",
"chapters": [
] };
case 7:
return { "mediaUrl": "Movie8.wmv",
"placeholderImage": "",
"chapters": [
] };
default:
throw Error.invalidOperation("No such mediainfo");
}
}
function StartWithParent(parentId, appId) {
new StartPlayer_0(parentId);
}
function StartPlayer_0(parentId) {
this._hostname = EePlayer.Player._getUniqueName("xamlHost");
Silverlight.createObjectEx( { source: 'player.xaml',
parentElement: $get(parentId ||"divPlayer_0"),
id:this._hostname,
properties:{ width:'100%', height:'100%', version:'1.0', background:document.body.style.backgroundColor, isWindowless:'false' },
events:{ onLoad:Function.createDelegate(this, this._handleLoad) } } );
this._currentMediainfo = 0;
}
StartPlayer_0.prototype= {
_handleLoad: function(plugIn) {
this._player = $create( ExtendedPlayer.Player,
{ // properties
autoPlay : true,
volume : 1.0,
muted : false
},
{ // event handlers
mediaEnded: Function.createDelegate(this, this._onMediaEnded),
mediaFailed: Function.createDelegate(this, this._onMediaFailed)
},
null, $get(this._hostname) );
//wire up the rollover and click events for each of our play buttons
for (var i = 0; i < cVideos; i++)
{
var element = plugIn.Content.findName('play' + i);
element.addEventListener("MouseEnter", Function.createDelegate(this,this._rollOver));
element.addEventListener("MouseLeave", Function.createDelegate(this,this._rollOut));
element.addEventListener("MouseLeftButtonUp", Function.createDelegate(this,this._playX));
}
plugIn.Content.findName('LeftArrow').addEventListener("MouseEnter", Function.createDelegate(this,this._rollOver));
plugIn.Content.findName('LeftArrow').addEventListener("MouseLeave", Function.createDelegate(this,this._rollOut));
plugIn.Content.findName('LeftArrow').addEventListener("MouseLeftButtonUp", Function.createDelegate(this,this._slideLeft));
plugIn.Content.findName('RightArrow').addEventListener("MouseEnter", Function.createDelegate(this,this._rollOver));
plugIn.Content.findName('RightArrow').addEventListener("MouseLeave", Function.createDelegate(this,this._rollOut));
plugIn.Content.findName('RightArrow').addEventListener("MouseLeftButtonUp", Function.createDelegate(this,this._slideRight));

this._playNextVideo();
},
_rollOver: function(sender, eventArgs) {
sender.opacity=1;
},
_rollOut: function(sender, eventArgs) {
sender.opacity=0.74;
},
_playX: function(sender, eventArgs) {
var X = Number(sender.Name.substring(4));
this._currentMediainfo = X;
this._player.set_mediainfo( get_mediainfo( X ));
sender.opacity=1;
},
_slideLeft: function(sender, eventArgs) {
switch(curPos)
{
case 1:
break;
default:
sender.findName("MoveRight0" + curPos).Begin();
curPos--;
}
},
_slideRight: function(sender, eventArgs) {
switch(curPos)
{
case maxPos:
break;
default:
sender.findName("MoveLeft0" + curPos).Begin();
curPos++;
}
},

_onMediaEnded: function(sender, eventArgs) {
//window.setTimeout( Function.createDelegate(this, this._playNextVideo), 1000);
},
_onMediaFailed: function(sender, eventArgs) {
alert(String.format( Ee.UI.Xaml.Media.Res.mediaFailed, this._player.get_mediaUrl() ) );
},
_playNextVideo: function() {
if (this._currentMediainfo<cVideos)
this._player.set_mediainfo( get_mediainfo( this._currentMediainfo++ ) );
}
}

First I added a reference to the plug-in the HandleLoad function, as described here. Then, because I had named all of my play buttons sequentially, it was easy to loop though them all adding some event handlers for rollover effects and the click event. Next I added similar event handlers to the navigation arrows. The rollover effect, as hinted at earlier was simply to set the opacity to 100% on mouseover and back to 74% on mouse out.

The click event for the play buttons simply play the selected movie, based on the number parsed from the name of the sender ("play0", "play1", etc.). The navigation arrows call the _slideLeft and _slideRight functions which simply play the animations to move the buttons left and right. if you have more than 2 pages of play buttons, then it gets slightly more complicated, obviously you have to create more animations, and they have to be carefully numbered so that you play the appropriate animation based on which 'page' of buttons you are currently on. Go to TheJamesBondMovies.com and take a look at the StartPlayer.js on that site for a better understanding of how to make this technique work with multiple pages.

Well, that was my solution, I'm sure there are other ways to do this, but I don't think this way is overly complicated and I hope someone finds it helpful.

Download the Project files: PlaylistSample.zip (364.40 kb)

Download the Silverlight 2 version: ScrollingPlaylist2.zip (1.22 mb)


Posted by Williarob on Wednesday, November 21, 2007 12:13 PM
Permalink | Comments (31) | Post RSSRSS comment feed