WP7: Notify user of new Application Version

UPDATE 7 NOV 2011: Have a look at this post: WP7: Northern Lights WP7 Toolkit v0.0.1 for the latest version of this code example.

Inspired by the great talk of Jeff Wilcox on TechEd Australia (video here) I dediced to implement a Application Version checker for my application before I publish my first version to the marketplace.

Jeff Wilcox is the maker of 4Th & Mayor, a successful Windows Phone 7 application. In his talk on TechEd Australia he mentioned some tips&tricks for future application developers. One of those tips is to build a version checker in your app so you can notify the users when there is a new version available for your app.

He has a lot of users that are running an old version of his app and he has no way to reach out to them and inform them that there is a new version with a lot of new features.

The following code should solve this problem.

I implemented the AppVersionManager class which gets the latest version number from a text-file online and compares this with the version number stored in your WMAppManifest.xml.

AppVersionManager.c

namespace MyApp.Utils
{
    using System;
    using System.ComponentModel;
    using System.Diagnostics;
    using System.IO;
    using System.Net;
    using System.Threading;

    /// 
    /// AppVersionManager class.
    /// 
    public class AppVersionManager
    {
        #region Fields
        private static string uri = "http://www.myapp.com/data/appversion.txt";
        #endregion

        #region Public methods
        /// 
        /// Check for new version of app and execute action when new version is available.
        /// 
        /// The action to execute when there is a new version available.
        public static void CheckForNewVersion(Action action)
        {
            BackgroundWorker bgw = new BackgroundWorker();
            bgw.DoWork += (sender, e) =>
            {
                Version appVersion = GetVersion();

                DoWebRequest(AppVersionManager.uri, appVersion, action);
            };

            bgw.RunWorkerAsync();
        }
        #endregion

        #region Private methods
        private static Version GetVersion()
        {
            string version = StateManager.Get("AppVersion");

            if (string.IsNullOrEmpty(version))
            {
                version = Utils.General.GetVersion();
                StateManager.Set("AppVersion", version);
            }

            try
            {
                return new Version(version);
            }
            catch
            {
            }

            return default(Version);
        }

        private static void DoWebRequest(string uri, Version appVersion, Action action)
        {
            string id = "appversionrequest";

            if (action == null)
            {
              return;
            }

            Timer t = null;
            int timeout = 60; // in seconds

            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri);
                request.Accept = "*/*";
                request.AllowAutoRedirect = true;
                
                // disable caching.
                request.Headers["Cache-Control"] = "no-cache";
                request.Headers["Pragma"] = "no-cache";

                t = new Timer(
                    state =>
                    {
                        if (string.Compare(state.ToString(), id, StringComparison.InvariantCultureIgnoreCase) == 0)
                        {
                            Debug.WriteLine(string.Format("Timeout reached for connection [{0}], aborting download.", id));

                            request.Abort();
                            t.Dispose();
                        }
                    },
                    id,
                    timeout * 1000,
                    0);

                request.BeginGetResponse(
                    r =>
                    {
                        try
                        {
                            if (t != null)
                            {
                                t.Dispose();
                            }

                            var httpRequest = (HttpWebRequest)r.AsyncState;
                            var httpResponse = (HttpWebResponse)httpRequest.EndGetResponse(r);

                            if (httpResponse.StatusCode == HttpStatusCode.OK)
                            {
                                using (StreamReader reader = new StreamReader(httpResponse.GetResponseStream()))
                                {
                                    string response = reader.ReadToEnd();

                                    try
                                    {
                                        Version currentVersion = new Version(response);

                                        if (appVersion < currentVersion)
                                        {
                                            action();
                                        }
                                    }
                                    catch
                                    {
                                    }
                                }
                            }
                            else
                            {
                                Debug.WriteLine(string.Format("Error occured accessing endpoint: {0} [{1}]", httpResponse.StatusCode, uri));
                            }
                        }
                        catch
                        {
                            Debug.WriteLine(string.Format("Error occured accessing endpoint: [{0}]", uri));
                        }
                    },
                    request);
            }
            catch
            {
            }
        }

        #endregion
    }
}

the Utils.General.GetVersion() implementation

        public static string GetVersion()
        {
            Uri manifest = new Uri("WMAppManifest.xml", UriKind.Relative);
            var si = Application.GetResourceStream(manifest);
            if (si != null)
            {
                using (StreamReader sr = new StreamReader(si.Stream))
                {
                    bool haveApp = false;
                    while (!sr.EndOfStream)
                    {
                        string line = sr.ReadLine();
                        if (!haveApp)
                        {
                            int i = line.IndexOf("AppPlatformVersion=\"", StringComparison.InvariantCulture);
                            if (i >= 0)
                            {
                                haveApp = true;
                                line = line.Substring(i + 20);
                                int z = line.IndexOf("\"");
                                if (z >= 0)
                                {
                                    // if you're interested in the app plat version at all                      
                                    // AppPlatformVersion = line.Substring(0, z);                    
                                }
                            }
                        }

                        int y = line.IndexOf("Version=\"", StringComparison.InvariantCulture);
                        if (y >= 0)
                        {
                            int z = line.IndexOf("\"", y + 9, StringComparison.InvariantCulture);
                            if (z >= 0)
                            {
                                // We have the version, no need to read on.                    
                                return line.Substring(y + 9, z - y - 9);
                            }
                        }
                    }
                }
            }

            return "Unknown";
        }

The contents of the appversion.txt file

1.1.0.0

So know you can call AppVersionManager from your MainPage.xaml to check for a new version and execute whatever you want to notify the user of your new version of the application.

            AppVersionManager.CheckForNewVersion(() =>
            {
                // notify user of new version.
            });

Probably the best way is to show a popup and ask the user if he wants to upgrade and then navigate him to the MarketPlace.

Alright, that's all. I hope you like it! Feedback is welcome.

UPDATE: added 'if (action == null)' in the top of DoWebRequest method to skip webrequest when there is no action specified. Thanks to @peSHIr.

9 Comments.

  1. WP7: Notify user of new Application Version - pingback on 2011/10/03 at 8:35 am
  2. Thank you – nice blog post.
    I tried to use the code: The StateManager is missing from the code. I assume it is just a cache, so you only need to make the web call once???

  3. bjornkuiper

    Yes, it’s just a cache. It’s mentioned in a previous post.

    I’m not sure the current StateManager that i used for this code example is completely the same, but it’s based on this example:

    http://bjorn.kuiper.nu/2011/08/03/wp7-phoneapplicationservice-current-state-made-easy/

    Let me know if it doesn’t work, so i can update the code.

  4. I’m trying to implement this and can’t get it to work. In debugging, it seems to work, as it processes the action, but I can’t get anything to show on the UI.

    I tried a messagebox.show where you have
    // notify user of new version.
    I also tried creating a Visibility property in the VM, setting a textblock with visibility set to collapsed, and where you have
    // notify user of new version.
    I set the visibility property to visible in the VM, ensured that it notifies of the property changed, and the textblock still does not become visible.

    Is there something I need to know about to deal with the UI since this is a background worker that is setting the visibility property?

    Thanks – I can’t wait to get this to work, but I’m stumped at the moment.

  5. I did change the GetVersion method because I am already using the ManifestAppInfo class, that Joost van Schaik wrote at http://dotnetbyexample.blogspot.com/2011/03/easy-access-to-wmappmanifestxml-app.html.

    My GetVersion method looks like the following, but that shouldn’t make a difference. The method returns what I would expect it to.
    private static Version GetVersion()
    {
    ManifestAppInfo info = new ManifestAppInfo();
    string version = info.Version;

    try
    {
    return new Version(version);
    }
    catch
    {
    }

    return default(Version);
    }

  6. I like Joost’s implementation as well. I also already updated the implementation, but that’s not represented in this post but available on codeplex: http://northernlights.codeplex.com

    Regarding your problem of showing a notification to the user. You might want to consider using my Notification Control which is also part of the Northern Lights library. There is an example in the code. You will need to make sure you are executing visible (UI) items on the UI Thread otherwise your application will throw an exception. You can access the UI thread by doing the following:

    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
    // your UI stuff
    });

    I hope that helps!

  7. That did it! Thanks! I’ll look at the Northern Lights for my next project. Thanks again!

  8. Hi,this method need upload txt file to a website space, and need to maintain txt file.

    Do you have any free space to share?

    Thanks,
    Jimmy

    • Hi Jimmy, you could also just use the option to let the user e-mail it to you. Sadly enough I haven’t room to spare. But have a look at leaseweb.com. They have cheap/reliable hosting available.

Trackbacks and Pingbacks: