Showing posts with label Tips and Tricks. Show all posts
Showing posts with label Tips and Tricks. Show all posts

Monday, 31 January 2011

Using DeferredLoadListBox in a Pivot Control

Recently I've been using the DeferredLoadListBox It's a fantastic way of improving the performance of your list views.


I started using the DeferredLoadListBox within a Pivot control but occasionally found that the app would randomly crash throwing the following exception:

All containers must have a Height set (ex: via ItemContainerStyle), though the heights need not all need to be the same.
This usually means you haven't set the properly. However I had ensured I had set the style height. I then got the source and started poking around.


What I found was although in the UnmaskItemContent method the container had a height if you inspected the ActualHeight property this was 0. I did a bit of research and found the following on MSDN:

ActualWidth and ActualHeight are calculated based on the Width/Height property values and the layout system.  There is no guarantee as to when these values will be "calculated"
So the problem appeared to be with timing.


After contacting the very helpful David Anson and exchanging a few ideas on solving the issue I decided to implement a retry counter. The concept being the first two times we find the container height is 0 drop out of the unmask method and try again later. If we still don't have a height on the third attempt an error must have occurred.


Implementing the counter was very simple and the most important thing is that this simple retry counter allows time for the height to be calculated and work as expected. The only minor side effect of this counter is that you can notice a slight pause in the UI however as this exception doesn't occur everytime this seems fair enough.


Find the modified source below and I hope it can be of use to you.

// Copyright (C) Microsoft Corporation. All Rights Reserved.
// This code released under the terms of the Microsoft Public License
// (Ms-PL, http://opensource.org/licenses/ms-pl.html).

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Media;

namespace Delay
{
    /// 
    /// Implements a subclass of ListBox based on a StackPanel that defers the
    /// loading of off-screen items until necessary in order to minimize impact
    /// to the UI thread.
    /// 
    public class DeferredLoadListBox : ListBox
    {
        private enum OverlapKind { Overlap, ChildAbove, ChildBelow };

        private ScrollViewer _scrollViewer;
        private ItemContainerGenerator _generator;
        private bool _queuedUnmaskVisibleContent;
        private bool _inOnApplyTemplate;

        /// 
        /// Handles the application of the Control's Template.
        /// 
        public override void OnApplyTemplate()
        {
            // Unhook from old Template elements
            _inOnApplyTemplate = true;
            ClearValue(VerticalOffsetShadowProperty);
            _scrollViewer = null;
            _generator = null;

            // Apply new Template
            base.OnApplyTemplate();

            // Hook up to new Template elements
            _scrollViewer = FindFirstChildOfType(this);
            if (null == _scrollViewer)
            {
                throw new NotSupportedException("Control Template must include a ScrollViewer (wrapping ItemsHost).");
            }
            _generator = ItemContainerGenerator;
            SetBinding(VerticalOffsetShadowProperty, new Binding { Source = _scrollViewer, Path = new PropertyPath("VerticalOffset") });
            _inOnApplyTemplate = false;
        }

        /// 
        /// Determines if the specified item is (or is eligible to be) its own item container.
        /// 
        /// The specified item.
        /// true if the item is its own item container; otherwise, false.
        protected override bool IsItemItsOwnContainerOverride(object item)
        {
            // Check container type
            return item is DeferredLoadListBoxItem;
        }

        /// 
        /// Creates or identifies the element used to display a specified item.
        /// 
        /// A DeferredLoadListBoxItem corresponding to a specified item.
        protected override DependencyObject GetContainerForItemOverride()
        {
            // Create container (matches ListBox implementation)
            var item = new DeferredLoadListBoxItem();
            if (ItemContainerStyle != null)
            {
                item.Style = ItemContainerStyle;
            }
            return item;
        }

        /// 
        /// Prepares the specified element to display the specified item.
        /// 
        /// The element used to display the specified item.
        /// The item to display.
        protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
        {
            // Perform base class preparation
            base.PrepareContainerForItemOverride(element, item);

            // Mask the container's content
            var container = (DeferredLoadListBoxItem)element;
            if (!DesignerProperties.IsInDesignTool)
            {
                container.MaskContent();
            }

            // Queue a (single) pass to unmask newly visible content on the next tick
            if (!_queuedUnmaskVisibleContent)
            {
                _queuedUnmaskVisibleContent = true;
                Dispatcher.BeginInvoke(() =>
                {
                    _queuedUnmaskVisibleContent = false;
                    UnmaskVisibleContent();
                });
            }
        }

        private static readonly DependencyProperty VerticalOffsetShadowProperty =
            DependencyProperty.Register("VerticalOffsetShadow", typeof(double), typeof(DeferredLoadListBox), new PropertyMetadata(-1.0, OnVerticalOffsetShadowChanged));
        private static void OnVerticalOffsetShadowChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
        {
            // Handle ScrollViewer VerticalOffset change by unmasking newly visible content
            ((DeferredLoadListBox)o).UnmaskVisibleContent();
        }

        private void UnmaskVisibleContent()
        {
            // Capture variables
            var count = Items.Count;

            // Find index of any container within view using (1-indexed) binary search
            var index = -1;
            var l = 0;
            var r = count + 1;
            while (-1 == index)
            {
                var p = (r - l) / 2;
                if (0 == p)
                {
                    break;
                }
                p += l;
                var c = (DeferredLoadListBoxItem)_generator.ContainerFromIndex(p - 1);
                if (null == c)
                {
                    if (_inOnApplyTemplate)
                    {
                        // Applying template; don't expect to have containers at this point
                        return;
                    }
                    // Should always be able to get the container
                    var presenter = FindFirstChildOfType(_scrollViewer);
                    var panel = (null == presenter) ? null : FindFirstChildOfType(presenter);
                    if (panel is VirtualizingStackPanel)
                    {
                        throw new NotSupportedException("Must change ItemsPanel to be a StackPanel (via the ItemsPanel property).");
                    }
                    else
                    {
                        throw new NotSupportedException("Couldn't find container for item (ItemsPanel should be a StackPanel).");
                    }
                }
                switch (Overlap(_scrollViewer, c, 0))
                {
                    case OverlapKind.Overlap:
                        index = p - 1;
                        break;
                    case OverlapKind.ChildAbove:
                        l = p;
                        break;
                    case OverlapKind.ChildBelow:
                        r = p;
                        break;
                }
            }

            if (-1 != index)
            {
                // Unmask visible items below the current item
                for (var i = index; i < count; i++)
                {
                    if (!UnmaskItemContent(i))
                    {
                        break;
                    }
                }

                // Unmask visible items above the current item
                for (var i = index - 1; 0 <= i; i--)
                {
                    if (!UnmaskItemContent(i))
                    {
                        break;
                    }
                }
            }
        }

        private bool UnmaskItemContent(int index)
        {
            var container = (DeferredLoadListBoxItem)_generator.ContainerFromIndex(index);
            if (null != container)
            {
                // Return quickly if not masked (but periodically check visibility anyway so we can stop once we're out of range)
                if (!container.Masked && (0 != (index % 16)))
                {
                    return true;
                }
                // Check necessary conditions
                if (0 == container.Height)
                {
                    throw new NotSupportedException("All containers must have a Height set (ex: via ItemContainerStyle), though the heights need not all need to be the same.");
                }
                // If container overlaps the "visible" area (i.e. on or near the screen), unmask it
                if (OverlapKind.Overlap == Overlap(_scrollViewer, container, 2 * _scrollViewer.ActualHeight))
                {
                    container.UnmaskContent();
                    return true;
                }
            }
            return false;
        }

        private static bool Overlap(double startA, double endA, double startB, double endB)
        {
            return (((startA <= startB) && (startB <= endA)) ||
                    ((startB <= startA) && (startA <= endB)));
        }

        private static OverlapKind Overlap(FrameworkElement parent, FrameworkElement child, double padding)
        {
            // Get child bounds relative to parent
            var transform = child.TransformToVisual(parent);
            var bounds = new Rect(transform.Transform(new Point()), transform.Transform(new Point(/*child.ActualWidth*/ 0, child.ActualHeight)));
            // Return kind of overlap
            if (Overlap(0 - padding, parent.ActualHeight + padding, bounds.Top, bounds.Bottom))
            {
                return OverlapKind.Overlap;
            }
            else if (bounds.Top < 0)
            {
                return OverlapKind.ChildAbove;
            }
            else
            {
                return OverlapKind.ChildBelow;
            }
        }

        private static T FindFirstChildOfType(DependencyObject root) where T : class
        {
            // Enqueue root node
            var queue = new Queue();
            queue.Enqueue(root);
            while (0 < queue.Count)
            {
                // Dequeue next node and check its children
                var current = queue.Dequeue();
                for (var i = VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--)
                {
                    var child = VisualTreeHelper.GetChild(current, i);
                    var typedChild = child as T;
                    if (null != typedChild)
                    {
                        return typedChild;
                    }
                    // Enqueue child
                    queue.Enqueue(child);
                }
            }
            // No children match
            return null;
        }
    }
}

Saturday, 22 January 2011

Windows Phone 7: Security Exception on deactivate

This morning I have been finishing the tombstoning part of one of my Windows Phone applications I have been developing. Whilst testing I found that when the application deactivated or terminated that a SecurityException was thrown. Looking at the stack trace I noticed that it was occurring whilst trying to serialise some data to the Applications State store.


Now I knew I was putting some data into state so this wasn't to hard to find however, when I looked at what I was putting into state it was nothing more complicated than a custom type that exposed a collection of POCO classes. Why would this cause a security exception.


I decided to do a simple Google search for the exception "windows phone 7 security exception" and the second result looked similar: "c# - SecurityException was unhandled when using isolated storage" [http://stackoverflow.com/questions/4209280/securityexception-was-unhandled-when-using-isolated-storage]. So I had a look, and guessed that if you can't put internal classes into IsolatedStorage you also can't do it for ApplicationState, which makes sense when you think about it. So I made my type public and not internal and all was good in the world again.


Another week another tip.... I wonder what I will find out next week

Tuesday, 19 May 2009

Wasting Bandwidth One Image at a Time....

Content Management Systems are great, they allow the average Joe to have a great level of control over their website. Gone are the days of clients asking for static pages to be amended, we live in the database powered give the power of editing and updating content to the client


Our clients get to use Rich Text Editors like FCKEditor. They look and feel just like Microsoft Word, they can play with text, upload images, resize them simply by dragging them and are very often really happy.


However this often comes at a cost. Most RTE's by default simply resize images by sticking on the HTML img attributes height and width. As many people are aware this doesn't actually resize the image, it just simply tells the browser take this massive image and render it smaller. The end user still has to download the huge image, I have seen on some sites 2000px x 1200px images being downloaded and then only shown at 250px x 120px!, which can take a while to load dependant their internet connection and ultimately we the developers have to pay the bandwidth cost for those images.


Tonight I decided to come up with something I could put on my church websites to combat this. It's more to aid the overall user experience rather than bandwidth cost but it all helps ;)

The Solution

The solution is actually really simple.

  1. Take the CMS content from the database
  2. Before rendering it to the page, parse it into a HTML Parser
  3. Using the parser find all the image tags
  4. Using the height,width and src attributes to generate a new URL to an image resizer app
  5. Replace the images src attribute with the new URL
  6. Render the content to the page

Parsing HTML in ASP.Net really is easy nowadays. Previously it was a pain, regex would never work properly or you could risk parsing your page as XML but luckily there are now tons of 3rd party libraries. I chose HTML AGility Pack as alot of people seemed to recommend it on stackoverflow

For my solution I simply did the following:


HtmlDocument doc = new HtmlDocument();
doc.LoadHtml(page.body);
HtmlNodeCollection imgs = doc.DocumentNode.SelectNodes("//img");
if (imgs != null)
{
    foreach (HtmlNode img in imgs)
    {
        if (img.Attributes["height"] != null && img.Attributes["width"] != null)
        {
            HtmlAttribute src = img.Attributes["src"];
            string imgurl = src.Value;
            src.Value = String.Format("/loadImage.aspx?image={0}&action={1}&width={2}&height={3}", imgurl, "resizecrop",img.Attributes["width"].Value, img.Attributes["height"].Value);
        }
    }
}
mainContent.Text = doc.DocumentNode.InnerHtml;

So what's going on here? Well first I create a new HtmlDocument, provided by the HTMLAgilityPack. I then load my html from my db model, page.body, then I use a nice simple XPATH syntax to pull out any img tags within the content and stick them into a HTMLNode collection. Next after ensuring I have some nodes, I spin through each of them and check to see if they have a height and width attribute. If they do then I use these and the original image url to write a new url, in my case I have a page that does this, ideally though this should be a httpmodule or something to be nice and tidy. Then with the img tags updated I render the HtmlDocument back out to the page.


That's it, a few line's of code. Now I was concerned I may have slowed down my page, as parsing and spinning through tag's could be CPU intensive however I found on my machine and quickly playing with it, I noticed no real difference. In production I'd output cache the page for a period of time anyway. I noticed 100KB difference on one of my site's home pages and they weren't massive images, so I think it's well worth it.


There you have it, at the end of the day CMS's provide users with alot of power / flexibility but we have to ensure as developers that we provide systems that cater for users abusing the system, be this uploading massive images or otherwise. In the case of images simply having something that resizes the images on render keeps the frontend responsive whilst allowing the client to provide high resolution images if needed, in this case it wasn't alot of effort and the improvement alone was worth it.

Saturday, 28 March 2009

Google SiteMap Generator + Input validation failed Error

Since Google release their Google Site Map Generator I have been using it on my web server for the sites that I manage. Setting it up and getting it running was fine and I haven't had a problem, that was until this week.


This week I noticed that as I was only letting the generator update the sitemap from actual URL hits quite often a few of my sites aren't hit for a day at a time which was resulting in empty sitemaps. This then causes Google WebMaster Tools to whinge at you which isn't a good thing. So I decided to update my settings to include parsing my IIS Log Files in the hope it would use previous days ones and not generate blank files.


This is where I hit a road block. When ever I changed a setting and clicked save the generator would be really useful and tell me that "Input Validation Failed" and to basically sort myself out. I was confused to say the least as everything was fine, no field was highlighted as being erroneous so I ended up giving up and leaving it.


Today I came back to it and tried again but the same error occurred. So I started to poke around and decided to manually update the sitesetttings xml file, usually located: C:\Program Files (x86)\Google\Google Sitemap Generator\sitesettings.xml. And this is when I noticed the issue that was affecting me.


Each site within your IIS setup has a node in the sitesettings xml file, here it has information about it's host name, whether it's setup for sitemaps etc. But it also contains the location of the IIS log files regardless of whether you are parsing them or not.


Now a few weeks ago I decided to move all my sites log files from the default location of C:\WINDOWS\system32\LogFiles\{site} to a more convenient location, for this example lets say E:\LogFiles\{site}, now this was all well and good for IIS etc but upon creation of sites the Google SiteMap Generator is logging these locations. So when I had moved the log files the generator was still looking at the old location, a bit of guessing / how I would do it lead me to believe that upon saying parse log files for sitemaps the generator checks to see if it can read the log files, but as they have moved it cant find them and errors.


Now all I did to fix this was manually do a find and replace on the log file locations within sitesettings.xml saved the file and restarted the generator to find it was finally happy and working OK. Hopefully Google in the next release of this generator will remove this issue / make it clearer what is wrong. Ideally upon startup or even when you choose to use log file parsing it should look at IIS to see if the path to the log files is the same as it has, if not update it before it validates. This would save heartache for a few people at least.


So I'm now happy again with the generator, the problem wasn't that hard to fix and upon spotting it made alot of sense, it just shows what a little bit of investigating can do.

Thursday, 19 March 2009

MVC Snippets: must be a reference type in order to use it as parameter 'TModel' in the generic type or method 'System.Web.Mvc.ViewUserControl'

Recently I have been playing with ASP.NET MVC, in particular I have been building myself a new website. I thought it might be good to post any peculiar things / lessons I learn during this build. Tonight I stumbled across one of these lessons.


When you strongly type a view or partial view the type must be a reference type. Otherwise this means you get a HttpCompilation Error: "your data type" must be a reference type in order to use it as parameter 'TModel' in the generic type or method 'System.Web.Mvc.ViewUserControl'. Initially I couldn't figure out what this meant as I was passing my type through, it existed etc. However it was then I realised I had declared my type as a struct not a class.


If you are unsure of the difference between a class and a struct I recommend looking it up, the gist of it is that a class is a reference type and a struct isn't. As a struct isn't a reference type you can save memory due to it not having to allocate additional memory for referencing each object, this is great for short structures, but not for models in ASP.Net MVC

After changing my type to a class all was sorted and my view could compile again. A lesson learned, one of many I am sure ;)

Sunday, 15 February 2009

Parsing an XML Boolean

Today I came across a situation where I was taking a value from an XML file which is a boolean. Now being me I knew that someone would either use 1 or true to indicate this boolean value, I for example always use 1 and 0 but I know people that prefer the "proper" way of saying true or false, especially if you don't have an XSD handy.


The XML itself is fine however once I had loaded this XML file into my .Net application I needed to parse the value as a boolean. This is where I hit a roadblock. Bool.Parse will only parse "true" or "false" string values not "1" or "0".


A quick explore through various sources led me to find XmlConvert.ToBoolean(), which is part of System.XML. XmlConvert allows you to convert from XML Data Types to .Net Data Types and in the case of Boolean can convert 1 to true.


This saved me loads of time so I thought I'd post it up here for others to find and hopefully enjoy.

Saturday, 22 November 2008

Windows Vista + Loosing the Ability to Hibernate

I'm one of the few people I know that use the Hibernate feature on my laptop. I find its helpful when shutting down for the evening but want to resume exactly where I left off the next day. Sleep even S3 doesn't work for me as I often find my battery depletes as on this laptop it is shockingly poor.


The other day I was performing some basic maintainence and decided to run disk cleanup to see if it was any better than XP's counterpart, it ran well and asked me would I like to remove my hibernate file. This is where my troubles began, I expected the file to be recreated upon my next hibernate, however what did happen was hibernate ceased to exist on my start menu.


After much hunting through the Control Panel I was unable to find a definitive option to renable the hibernate file, power options was all setup to hibernate however I was still unable to hibernate. I then found a simple command line setting you need to run to renable it, or even turn it off if that is your desire.


First open a command prompt window in administrative mode, its annoying when you forget to do this, then simply run the following: powercfg -H ON. The -H switch indicates hibernate and then its a simple on off. And that's it Hibernate restored and my laptop is back to it's normal self, good stuff.

Monday, 6 August 2007

For When Youve Had Enough of Vista Backup

Windows Vista came with a brand new backup tool to help save millions of people from loosing all there beloved data. But what happens when the user doesnt want ever file known to man backed up or the categorisation version misses out too much information? Well when it comes to the Vista backup tool your stuffed. Thats all it does, All, or next to nothing. Gutted I decided to bin off Vista Backup tool and find a worthy replacement, amazingly it didnt take too long. Presenting Microsoft Sync Toy v1.4 ;) Amazingly SyncToy does exactly what I want to. I used to use it many moons ago for syncing my uni files on my Laptop with my Desktop PC, but since I left uni and Hayley "inherited" , stole, my laptop I havnt used it. Until tonight that was, I decided to start it up again, create all my folder shares, development, databases, documents, music and photos and then schedule it to run every week. Now on a weekly basis all my files are backed up to a spare internal HDD and an external HDD. When it comes to sync options, SyncToy has every mode under the sun, I decided to set all mine to contribute, so new files, updates and names changes are copied to my backup, however deletions arent, in case I get all shift + del happy ;) And thats it, just simple backed up goodness. Now if you are a fan of having all your information in a backup format MS can see when your reinstalling from a backup etc, why not use SyncToy to get all the data you do want onto a HDD in a suitable format and then get Vista Backup to make its own backup image of that drive, that way all the files you want, none of the ones you dont and no hassle. If only other people thought like me....

More ASP.Net Antics - using the Site Navigation with SQL - An Update

Well it's been a while since I've had something decent to blog about, however I warn you what I'm about to blog about isn't really decent, its more of a side comment / observation. A while a go I posted about using ASP.NET 2's site navigation control with SQL, I have been playing with my modified of this for a while, and all seemed to be working fine in demo's and general development. However last week an interesting bug appeared. When the site's application is restarted, i.e cache cleared and the site navigation is rebuilt, the site would crash out the first two times the navigation tries to rebuild. The third time however results in the site loading perfectly fine. I have to say this behaviour baffled me. The first thing I did was to download the site's live database and run against a more debug friendly version of the site's code. Now the error message being thrown up was that the current node / page had an invalid parent. So of course I then checked the database only to find it did have a valid parent and all was ship shape data wise. I decided to deactive this page only to find another page casued the error, so I disabled the next suspect along with 2 others. Now this all seemed very odd, why would some pages error and others not? I couldn't figure it out, however I did realise one situation where this bug appeared for a genuine reason. I decided to add an "active" field next to each page so you could turn off pages until they were ready to go live. Now you can do this for a parent page, however all the child pages are left active. This would then oviously cause the script to chuck out errors as our SQL that returns all the pages for the navigation doesn't check to see if a pages parent is active. So how do you cater for this situation? Well I thought of a couple of ways, you could make your SQL, be it a stored procedure or plain T-SQL statement, join the parent Id's using an Inner Join and ensure the parents are active, this way any inactive parents are left off. However if you have a lot of pages in your database and the site has a steady flow of traffic, you could end up with a potential bottleneck. You could ensure that when a page is deactivated that all it's child pages and then their child pages are deactivaed, again this would be crazy as if you have lots of child pages which than have further child pages you would end up with unrelistic amount of update statements to perform. So what is the answer? Well i decided to do the required work at the business logic layer of my system. We have our SQLSiteMapProvider and it checks to see that the Parent ID of a node is valid. At present this churns out a nice provider exception error message so I decided to update this to handle things differently. I decided to go down the root of instead of causing a provider error if the parent id is invalid then simply return null instead of a sitemap node for the current page. Then where we call the check if parent is valid function, check if null is returned. If it isn't add the node to our sitemap, if not then don't worry carry on. This way any orphaned children are just ignored and the end user knows no difference. Now before anyone else comments, I know this isn't a grand solution and I'm sure that there will be pros and con's for a SQL solution and this solution or maybe even a better way, please comment if you have a better way, however for the hour I had to look at this I felt this was the best solution. It even masked the random page error that was occuring at the start, hopefully when I get chance to rebuild my main site using this code I will be able to take some time to relook at this provider and come up with a better solution. Until then if you want a copy of the SQLSiteMapProvider I am using then grab this zip file.

Thursday, 24 May 2007

More Vista Tips - IIS 7 and Visual Studio 2005

IIS 7 Woes

Tonight I decided to carry on with Old Roan's site, I havn't touched it since I reinstalled Vista on the weekend. Everything is running pretty sweet now, even got my "New Day" office 2007 installed. All was grand. Until I resetup the old roan site in IIS7. When I tried to view the site: oldroan.mjjamesdev.mj for example I got a nice: HTTP Error 500.19 - Internal Server Error Yummy, after about 10 mins of faffing I realised that the security permissions on the folder were set to, Administrators, Creator Owner and my user only. When copying the data back to my main drive off my backup the Users group got lost. So a quick add user group with read and execute permission solved everything.

Visual Web Developer Express 2005 Woes

By default, the VS 2005 range including the express editions don't like IIS7. When you try to open a site that you have running in IIS7 its says the product is not compatable. I was gutted to hear this but for the past month I have been using Vista I never really thought much about it as I know 'Orcas ' is only around the corner. However stumbled upon Scott Gu's Blog article detailing how to get it all working. Needless to say this is great and really does help :D