Devlico.Us
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @devlicious

ViNull, Off the Record

Distilled ramblings of Michael C. Neel delivered right to your browser.

April 2008 - Posts

  • SilverLight Interop with Flash/Flex (flashlight?)

    imageWhile the marketers and academics focus on the battle between silverlight and flash/flex, here in Mike's Mad Scientist Labs we've been focused on making the two work together.  Like our founder (Dr. Frankenstein) the implementation is a bit rough, but we can proudly proclaim, "It's Alive!"

    Before I get into my lab notes, let's talk about the basic approach and why.  Silverlight is awesome, but it's the new kid and developers won't always be able to replace existing flash apps with silverlight, so a migration path is needed.  Also, sliverlight isn't going to play FLV - flash's proprietary video codec - anytime soon, and there is a multi-ton of video in this format.  If silverlight supported the WPF Frame element, which can render any HTML, things would be better, but as of today Frame isn't supported.

    The goal: a silverlight control list a choice of FLV videos, let the user choose one, and play that video in a flash control.  To communicate between the controls we'll use javascript. This approach is very similar to Jeff Prosise's post on silverlight interop, in which javascript allowed two silverlight controls to communicate.

    The code and XAML to the Silverlight control is very simple:

    <UserControl x:Class="MyVideos.Page"
        xmlns="http://schemas.microsoft.com/client/2007" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
        Width="400" Height="300">
        <ListBox x:Name="lbVideos" SelectionChanged="lbVideos_SelectionChanged">
            <TextBlock>Loading...</TextBlock>
        </ListBox>
    </UserControl>
    
    public partial class Page : UserControl {
        public List<String> OnPlayHandlers { get; set; }
    
        [ScriptableMember]
        public void addOnPlayHandler(String functionName) {
            OnPlayHandlers.Add(functionName);
        }
    
        protected void OnPlay(String video) {
            foreach (String h in OnPlayHandlers)
                HtmlPage.Window.Invoke(h, video);
        }
    
        public Page() {
            InitializeComponent();
            OnPlayHandlers = new List<string>();
    
            HtmlPage.RegisterScriptableObject("Page", this);
    
            lbVideos.Items.Clear();
            String[] items = new String[] { "hampster.flv", 
                                            "smb_flute.flv", 
                                            "starwarskid.flv" };
            foreach (String s in items)
                lbVideos.Items.Add(s);
        }
    
        private void lbVideos_SelectionChanged(object sender, SelectionChangedEventArgs e) {
            OnPlay(lbVideos.SelectedItem.ToString().Replace("Video: ",String.Empty));
        }
    }
    
    

    The [ScriptableMember] attribute exposes this method to javascipt, and  HtmlPage.RegisterScriptableObject maps the method name, which will end up being SilverlightControl.Page.addOnPlayHandler.  I borrowed the common event handler pattern so it's easy to have multiple javascript methods fire when the user clicks a video - I also didn't want to make any assumptions about the flash control that would play the video.  HtmlPage.Window.Invoke is the real magic, which invokes the actual javascript function when a video is clicked.

    The ASPX/HTML is also pretty light:

    <asp:ScriptManager ID="ScriptManager1" runat="server"/>
    <asp:Silverlight ID="slMyVideos" runat="server" Source="~/ClientBin/MyVideos.xap"
        Version="2.0" Width="400px" Height="300px" OnPluginLoaded="slMyVideos_Loaded">
    </asp:Silverlight>
    <OBJECT classid='clsid:D27CDB6E-AE6D-11cf-96B8-444553540000'
            WIDTH='320' HEIGHT='260' id='FLVplayer'>
        <PARAM NAME=movie VALUE='flvplayer.swf'>
        <PARAM NAME=allowScriptAccess VALUE=sameDomain>
        <PARAM NAME=flashvars VALUE='file=dummy.flv&autostart=true&enablejs=true'>
        <EMBED src='flvplayer.swf' NAME='FLVplayer' 
               swliveconnect='true' WIDTH='320' HEIGHT='260'
               TYPE='application/x-shockwave-flash'
               FLASHVARS='file=dummy.flv&autostart=true&enablejs=true'>
        </EMBED>
    </OBJECT>
    

    I'm using the asp:Silverlight tag included with the silverlight 2.0 beta 1 tools to handle generating the object tags for the silverlight control.  I wrote out the object and embed tags for the flash player to show exactly what's going on (since I assume most of my reader are not Flash developers), but in a production app I recommend using the open source SWFObject project for better cross browser support.

    The critical lines for flash are allowScriptAccess and swliveconnect which enable javascript control of the flash.  I'm using the open source FLVplayer by Jeroen Wijering, which requires a flashvar of enablejs=true in addition to the above.  The FLVplayer has a rich Javascript API for controlling the play, so the last step is to add some javascript to connect the OnPlay event of the silverlight control to load and play the video in the flash control.

    <script type="text/javascript">
    
        function slMyVideos_Loaded() {
            var sl = $get('<%= slMyVideos.ClientID %>');
            sl.Content.Page.addOnPlayHandler('OnPlay');
        }
    
        function getFlashMovieObject(movieName) {
            if (document.embeds && document.embeds[movieName])
                return document.embeds[movieName]; 
            else
                return $get(movieName);
        }    
    
        function OnPlay(video) {
            flvplayer = getFlashMovieObject('FLVplayer');
            try {
                flvplayer.loadFile({file: video});
                flvplayer.sendEvent('playitem', 0);   
            }
            catch(e) {
                alert(e.description);                
            }
        }
    
    </script>
    

    The slMyVideos_Loaded function is called when the silverlight control has fully loaded (by the OnPluginLoaded="slMyVideos_Loaded" set in the silverlight tag).  The getFlashMovieObject function determines if the browser is using the embed version or the object version of the flash control, and returns the correct reference.  The OnPlay function is called by silverlight when the user clicks an item in the list and uses the FLVPlayer's API to load and play the video.

    It is possible to run Javascript right from flash and silverlight to talk directly to one another without the need for this glue code on the page.  This would tightly couple the two controls, and isn't my cup of tea, but I can see some instances where that would be a desired behavior.

    Now back to the lab to see what else we can bring to life...

  • Session, ForEach, and a ShallowCopy walk into a bar...

    imageBefore we get started, let's play "questions you only hear during an interview."   Are the contestants ready?  Good, here is the question:  What is the difference between a shallow copy and a deep copy? 

    MSDN on Object.MemberwiseClone: "Consider an object called X that references objects A and B. Object B, in turn, references object C. A shallow copy of X creates new object X2 that also references objects A and B. In contrast, a deep copy of X creates a new object X2 that references the new objects A2 and B2, which are copies of A and B. B2, in turn, references the new object C2, which is a copy C."

    Clear as mud?  Okay, think of it this was - if you made a shallow copy of a directory it would copy only the files in the top level of the directory and create shortcut links to the subfolders.  A deep copy would copy the files, all subfolders and their file, and the subfolder's subfolders and files, etc.  In C#/.Net world, 99% of the time you are making shallow copies.

    It common for me to have an asp.net app that gets data from a webservice, and then applies filters selected by the user.  Since the user will change sorting, filters, etc with the same set of data several times, I cache the call to the webservice in Session.  (Note: It's okay because I expect only 2-4 user's at a time on these, mostly internal applications - don't do this if you need to scale beyond a small set of users!).  It looks something like:

        public ShowProduct[] Products { 
    get {
    return Session["Products"] == null ?
    new ShowProduct[0] :
    (ShowProduct[])Session["Products"];
    }
    set {
    Session["Products"] = value;
    }
    }

    protected void LoadProducts()
    {
    ShowServiceSoap svc = new ShowServiceSoap();
    Products = svc.GetProductsByShowID(
    String.Empty,
    Convert.ToDecimal(ddlShows.SelectedValue));
    }

    protected void FilterProducts() {
    ShowProduct[] products = Products;

    if (cbFilterSizeable.Checked) {
    products = (from p in products
    where p.Sizeable.Equals(true)
    select p).ToArray();
    }

    gvProducts.DataSource = products;
    gvProducts.DataBind();
    }

    This works just fine.  In practice, there are many steps in the FilterProducts method and I add some dependency injection options beyond using a property to access session, but you get the idea.  These methods are called from event handlers on the web form. 

    Now, I had a request to add an option to filter product videos to only .AVI, which are stored in the ShowProduct.Video list, so I changed FilterProducts like so:

        protected void FilterProducts() {
    ShowProduct[] products = Products;

    if (cbFilterSizeable.Checked) {
    products = (from p in products
    where p.Sizeable.Equals(true)
    select p).ToArray();
    }

    if (cbFilterAviOnly.Checked) {
    foreach (ShowProduct p in products)
    p.Videos = (from v in p.Videos
    where v.FileType.Equals("AVI",
    StringComparison.CurrentCultureIgnoreCase)
    select v).ToArray();
    }

    gvProducts.DataSource = products;
    gvProducts.DataBind();
    }

    If you see the error, and understand why it happens, congratulations!  If you are like me you noticed that when this runs, and the user filters AVI only, all works as expected.  When they remove the filter however, the non-AVI files do not return.  More confusing is that if they filtered Sizeable products only, and the removed the filter, the missing product did return!  What's going on?

            ShowProduct[] products = Products;

    This made a shallow copy of the array in Session.  Each ShowProduct was copied, but the Videos array was a reference.  When I changed the Videos array in the shallow copy, it changed the object in Session because both Session and my copy pointed at the same location in memory.

    I searched around for a bit to find a way to solve my problem; i.e. how to make a deep copy in C#/.Net.  There is a method System.FantasyFramework.DeepCopy that will take any object type and return a deep copy of that object back, but I can't get my Visual Studio to find the dll for System.FantasyFramework in the GAC.  Many people have this issue as well, and resort to implementing ICloneable on their object so that the object deep copies itself instead of shallow copies or write an extension method to deep copy the Array class.  I however opted for the quick fix below:

        public ShowProduct[] Products { 
    get {
    if (Session["ShowProducts"] == null)
    return null;

    Object result = null;
    using (MemoryStream ms = new MemoryStream()) {
    BinaryFormatter bf = new BinaryFormatter();
    bf.Serialize(ms, Session["ShowProducts"]);
    ms.Seek(0, SeekOrigin.Begin);
    result = bf.Deserialize(ms);
    ms.Close();
    }

    return (ShowProduct[])result;

    }
    set {
    Session["Products"] = value;
    }
    }

    When accessed, the property loads the shallow copy from Session, then Serializes it to memory, then load it's back and returns the result.  This "washes" away any references and makes a full deep copy of the array.  (You do remember the part about not needing this to scale right?  Cool, do don't this on your public website's homepage).  This is also a case where good TDD practices have benefits beyond making it easier to write unit tests.

    As for the end of the bar joke, I'm afraid I don't have a punch-line yet.  I think it will have something to do with Session drinking only water, while ForEach and ShallowCopy see who can do the most shots of tequila - until Session falls over drunk.

  • .Net routine file IO best practice

    image File IO has shaped the computer industry more than any other technology.  Doubt me?  Consider that in 1978 AT&T decided to no longer share the source code of Unix with universities, upsetting professors like Andrew Tanenbaum.  Andrew taught cources on operating systems and felt it was impossible to teach the subject without the source code.  Why?  Some concepts are harder to grasp in theory but easy in code, like threading.  Other concepts are easy in theory, but hold a world of problems in implementation, like file IO.  To solve this, Andrew created MINUX, a "minimal Unix" system for teaching.  Later, a young lad in Finland named Linus started adding features to MINUX and jokingly referred to as "Linus' Minux", a name that stuck as Linux.

    So what's so hard about file IO that requires a working example to learn?  After all, you open a file, read or write some data, then close it - simple right?  Consider what goes on when you call File.Open - the OS uses an index to figure out where the bits to the requested file are stored - and they may not be all stored in the same place.  A magnetic disk begins to spin while a mechanical arm is positioned at the spot the OS designates.  Electric current is generated from the variations in the magnetic field, passed over a controller to the CPU to make sense of, before passing it along to your application. 

    That's just the hardware, there may be more software involved in that operation.  Virus scanners may kick in to make sure the file is safe, or network users may be half way though copying the file.  Can your application handle the user snooping around with Notepad while you save some data?  Nothing can tick off a user more than a corrupted save file, loosing hours of work in your application.

    "Okay Mike, I get it - shut up and show me the code."

    The methods I present here are designed to save and read objects as XML.  I came up with these after going though numerous methods in the .Net Framework (it seems every base class has at least one file IO method these days), figuring out what worked best.

    using System;
    using System.Text;
    using System.IO;
    using System.Xml;
    using System.Xml.Serialization;
    using System.Threading;
    
    public class IOTools {
    
        public static void SerializeObject(String FileName, Type ObjectType, Object Data) {
            Double timeout = 5000;
    
            StringBuilder xmlData = new StringBuilder();
            XmlSerializerNamespaces xsn = new XmlSerializerNamespaces();
            xsn.Add(String.Empty, String.Empty);
    
            StringWriterUTF8 stringWriter = new StringWriterUTF8(xmlData);
            XmlTextWriter xmlWriter = new XmlTextWriter(stringWriter);
            XmlSerializer xmlSerial = new XmlSerializer(ObjectType);
            xmlSerial.Serialize(xmlWriter, Data, xsn);
            xmlWriter.Close();
    
            DateTime start = DateTime.Now;
            Exception lastEx = null;
            Boolean success = false;
    
            while (DateTime.Now < start.AddMilliseconds(timeout)) {
                try {
                    using (FileStream fs = File.Open(FileName, FileMode.Create, FileAccess.Write, FileShare.None)) {
                        StreamWriter writer = new StreamWriter(fs);
                        writer.Write(xmlData.ToString());
                        writer.Close();
                        success = true;
                        break;
                    }
                }
                catch (Exception ex) {
                    lastEx = ex;
                    Thread.Sleep(10);
                }
            }
    
            if (!success) throw lastEx;
        }
    
        public static object DeserializeObject(String FileName, Type ObjectType) {
            Double timeout = 5000;
            String data = String.Empty;
    
            DateTime start = DateTime.Now;
            Exception lastEx = null;
            Boolean success = false;
    
            while (DateTime.Now < start.AddMilliseconds(timeout)) {
                try {
                    using (FileStream fs = File.Open(FileName, FileMode.Open, FileAccess.Read, FileShare.Read)) {
                        StreamReader reader = new StreamReader(fs);
                        data = reader.ReadToEnd();
                        fs.Close();
                        success = true;
                        break;
                    }
                }
                catch (Exception ex) {
                    lastEx = ex;
                    Thread.Sleep(10);
                }
            }
    
            if (!success) throw lastEx;
    
            StringReader stringReader = new StringReader(data);
            XmlTextReader xmlReader = new XmlTextReader(stringReader);
            XmlSerializer xmlSerial = new XmlSerializer(ObjectType);
            return xmlSerial.Deserialize(xmlReader);
        }
    
        public class StringWriterUTF8 : StringWriter {
            public override Encoding Encoding {
                get { return Encoding.UTF8; }
            }
            public StringWriterUTF8() : base() { }
            public StringWriterUTF8(StringBuilder sb) : base(sb) { }
        }
    }

    To minimize the chance of accessing the files while they are in use, the data is buffered in a string (or StringBuilder).  The basic logic boils down to this: try to open the file for 5 seconds, and if you can't throw an exception.  In testing I found a Thread.Sleep of just 10 milliseconds was enough that the process didn't show up in CPU usage (without Sleep, a locked file could cause 65% load for 5 seconds - ouch).

    There is some extra XML work going on to save the files without namespaces and in UTF-8 (by default, all strings are UTF-16).  Since my XML files aren't limited to being used by .Net apps this helps compatibility and the framework has a little known feature called "XML Namespace Hell" that's best to avoid.  If you were certain your data would only be read by you or other .Net apps, I recommend replacing the XML with the binary serialization methods of System.Runtime.Serialization for better performance.

    Using the methods looks like this:

    // Saving as XML
    MyCustomClass myClass = new MyCustomClass();
    try { SerializeObject(@"C:\data\myclass.xml", typeof(MyCustomClass), myClass); }
    catch { /* handle a failure */ }
    
    // Loading from XML
    MyCustomClass myClass = null;
    try { myClass = (MyCustomClass)DeserializeObject(@"C:\data\myclass.xml", typeof(MyCustomClass)); }
    catch { /* handle a failure */ }

    These methods work for local drives and network shares alike, File.Open is pretty liberal with the possibilities of a full path.  This is by no means asserted here as the final, full-proof way to handle routine file IO; if you have some field notes learned in battle please share them in the comment!

More Posts

Our Sponsors

Proudly Partnered With