An ASP.net AJAX UpdateProgress that can render Inline

I’ve been starting to pick up ASP.net AJAX again, mainly because as a SharePoint developer I will be “stuck” with WebForms 3.5 for the next few years (SharePoint 2010 will not be a .net 4.0 application). As part of that, I remembered one issue I had: The UpdateProgress renders a div with display:block set, which means it is not possible to have the updateProgress right next to a button. Joe Audette had the right idea: Grab the UpdateProgress from the Mono Project and modify it. The nice thing about the Class Libraries of Mono is that they are licensed under the very liberal MIT X11 license, making them good candidates as a base for modifications.

Here is the source code of the UpdateProgressEx, which includes a new Property, DisplayInline. If this is set to true, then we render a span with display: inline. If this is set to false or not set at all, we have the default behavior of a div/block. If DisplayInline is set to true, then we force DynamicLayout to be false. Why? Because if DynamicLayout is true, then it seems like the MS AJAX JavaScript control the display, and they render it as a block. As I did not want to modify it any further, I just force DynamicLayout to false because for me, that works good enough. Also, I haven’t tested if it works in visual design mode and if the property panel shows the new DisplayInline Property, as I work only in code view. YMMV, but it’s Open Source after all, so feel free to properly fix it 😛

Here is the code:

// UpdateProgressEx
//
// Author:
//  Michael Stum <website@stum.de>
//  https://www.stum.de/2010/01/28/an-asp-net-ajax-updateprogress-that-can-render-inline
//
// Original Author:
//   Igor Zelmanovich <igorz@mainsoft.com>
//   (C) 2007 Mainsoft, Inc.  http://www.mainsoft.com
//
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Web.UI;

namespace StumDE.WebControls
{
    [PersistChildren(false)]
    [ParseChildren(true)]
    [DefaultProperty("AssociatedUpdatePanelID")]
    [Designer("System.Web.UI.Design.UpdateProgressDesigner, System.Web.Extensions.Design, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35")]
    public class UpdateProgressEx : Control, IScriptControl
    {
        ITemplate _progressTemplate;
        ScriptManager _scriptManager;

        [Category("Behavior")]
        [DefaultValue("")]
        [IDReferenceProperty(typeof(UpdatePanel))]
        public string AssociatedUpdatePanelID
        {
            get
            {
                return (string)ViewState["AssociatedUpdatePanelID"] ?? String.Empty;
            }
            set
            {
                ViewState["AssociatedUpdatePanelID"] = value;
            }
        }

        [Category("Behavior")]
        [DefaultValue(false)]
        public bool DisplayInline
        {
            get
            {
                object o = ViewState["DisplayInline"];
                if (o == null)
                    return false;
                return (bool)o;
            }
            set
            {
                ViewState["DisplayInline"] = value;
            }
        }

        [Category("Behavior")]
        [DefaultValue(500)]
        public int DisplayAfter
        {
            get
            {
                object o = ViewState["DisplayAfter"];
                if (o == null)
                    return 500;
                return (int)o;
            }
            set
            {
                ViewState["DisplayAfter"] = value;
            }
        }

        [Category("Behavior")]
        [DefaultValue(true)]
        public bool DynamicLayout
        {
            get
            {
                // If DisplayInline is set, force dynamic layout to be false
                if(DisplayInline) return false;
                object o = ViewState["DynamicLayout"];
                if (o == null)
                    return true;
                return (bool)o;
            }
            set
            {
                ViewState["DynamicLayout"] = value;
            }
        }

        [PersistenceMode(PersistenceMode.InnerProperty)]
        [Browsable(false)]
        public ITemplate ProgressTemplate
        {
            get
            {
                return _progressTemplate;
            }
            set
            {
                _progressTemplate = value;
            }
        }

        ScriptManager ScriptManager
        {
            get
            {
                if (_scriptManager == null)
                {
                    _scriptManager = ScriptManager.GetCurrent(Page);
                    if (_scriptManager == null)
                        throw new InvalidOperationException(String.Format("The control with ID '{0}' requires a ScriptManager on the page. The ScriptManager must appear before any controls that need it.", ID));
                }
                return _scriptManager;
            }
        }

        protected virtual IEnumerable<ScriptDescriptor> GetScriptDescriptors()
        {
            string updatePanelClientId;
            if (String.IsNullOrEmpty(AssociatedUpdatePanelID))
                updatePanelClientId = null;
            else
            {
                var updatePanel = FindControl(AssociatedUpdatePanelID) as UpdatePanel;
                if (updatePanel == null)
                    throw new InvalidOperationException("No UpdatePanel found for AssociatedUpdatePanelID '" + AssociatedUpdatePanelID + "'.");
                updatePanelClientId = updatePanel.ClientID;
            }
            var descriptor = new ScriptControlDescriptor("Sys.UI._UpdateProgress", this.ClientID);
            descriptor.AddProperty("associatedUpdatePanelId", updatePanelClientId);
            descriptor.AddProperty("displayAfter", DisplayAfter);
            descriptor.AddProperty("dynamicLayout", DynamicLayout);
            descriptor.AddProperty("displayInline", DisplayInline);
            yield return descriptor;
        }

        protected virtual IEnumerable<ScriptReference> GetScriptReferences()
        {
            yield break;
        }

        protected override void OnPreRender(EventArgs e)
        {
            base.OnPreRender(e);
            ScriptManager.RegisterScriptControl(this);

            if (_progressTemplate == null)
                throw new InvalidOperationException(String.Format("A ProgressTemplate must be specified on UpdateProgress control with ID '{0}'.", ID));

            var container = new Control();
            _progressTemplate.InstantiateIn(container);
            Controls.Add(container);
        }

        protected override void Render(HtmlTextWriter writer)
        {
            if (DynamicLayout)
                writer.AddStyleAttribute(HtmlTextWriterStyle.Display, "none");
            else
            {

                writer.AddStyleAttribute(HtmlTextWriterStyle.Display, DisplayInline ? "inline" : "block");
                writer.AddStyleAttribute(HtmlTextWriterStyle.Visibility, "hidden");
            }
            writer.AddAttribute(HtmlTextWriterAttribute.Id, ClientID);
            if (DisplayInline)
            {
                writer.RenderBeginTag(HtmlTextWriterTag.Span);
            }
            else
            {
                writer.RenderBeginTag(HtmlTextWriterTag.Div);
            }
            base.Render(writer);
            writer.RenderEndTag();

            ScriptManager.RegisterScriptDescriptors(this);
        }

        #region IScriptControl Members

        IEnumerable<ScriptDescriptor> IScriptControl.GetScriptDescriptors()
        {
            return GetScriptDescriptors();
        }

        IEnumerable<ScriptReference> IScriptControl.GetScriptReferences()
        {
            return GetScriptReferences();
        }

        #endregion
    }
}

WSSv3/Sharepoint 2007 does not support nested master pages

For a Project I had on a SharePoint site I thought it would be a good idea to create a new master page that is based on the default.master and exposes some placeholder for my content pages to use. Nested Master Pages are trivial to do in ASP.net normally, but for some reason on my SharePoint site, the child page was not rendering its content in one of the new placeholders.

Well, turns out that SharePoint 2007/WSSv3 explicitly does not support nested master pages. To quote an Article in MSDN directly:

Although the underlying technology enables the creation of nested master pages, nested master pages are not supported in Windows SharePoint Services. Using nested master pages can cause unexpected behavior in some scenarios, such as preventing content from rendering.

Indeed, unexpected behavior, such as content not rendering is the exact problem I have. Now I remember why I love SharePoint so much: It’s taking the essential functions of ASP.net and then adds some traps that really hurt you unexpectedly.

Beginning Debugging in VS2010 Episode 1: Breakpoints and Locals

The first episode in the Debugging in Visual Studio 2010 series is about the most fundamental concept of all, Breakpoints. Also, we cover the Locals window, which is the first additional window of the debugger.

As this is the first episode, there are some kinks I haven’t manage to fully iron out. The audio quality isn’t perfect, mainly because the Microphone I specifically bought for stuff like this turned out to be junk and I had to use the one in my Webcam. Also I experimented with having a script vs. speaking freely and tend to overuse the phrase “You may notice” quite a bit…

You can watch the video in higher, full HD quality directly on Vimeo.

Beginning Debugging in VS2010 Episode 1: Breakpoints and Locals from Michael Stum on Vimeo.

The source code for the demo application is available here.

Debugging in Visual Studio 2010 Video Series

I found that if I want to learn something, I should try explaining or teaching it to someone else. As I work as a SharePoint Developer, the Debugger became my best friend very quickly, but I always ever only touched the surface and maybe a little bit more. A recent problem requires me now to dig really deep and is forcing me to really learn to use the debugger. So I thought this would be an excellent opportunity to create a little video series, of which the first chapter will be “Beginning Debugging in Visual Studio 2010”. Now let me first start with a disclaimer: I am still learning this stuff. I am not a senior developer at Microsoft with 20 years experience writing operating system, I am just someone who picked up C# 4 years ago and is now trying to learn through teaching.

This series is not about design or architecture, it is not about unit testing or best practices. This is purely about having some buggy code and using the debugger to find the issue and solve it. This is about moving from trial-and-error/guessing to evidence-gathering.

There is no set schedule for releases and I am not sure how many episodes there will be. I do have plans for three chapters of varying “difficulty” and I do have a to-do list of stuff I want to cover, but as some items on this list are really hardcore I’m not sure what will come out of this.

Anyway, here is an index to all released episodes:

A simple Even/Odd Cycler for .net

If you want a table with different CSS Styles for even/odd rows, that is reasonably easy, usually you have code like this:

bool evenRow = true;
foreach (var item in someList)
{
  evenRow = !evenRow;
  SomeTable.Rows.Add(SomeFunctionThatReturnsATableRow(item,evenRow?"evenRow":"oddRow"));
}

So we have a bool that we switch on every iteration and then we derive the CSS Class Name from that. If you do that in one place, it’s okay, but if you have to do that in many places, it can become quite a bit tedious. Sure, if you happen to use a for-loop you can just use “index % 2 == 0” to find out if you are on an even row, but I instead created a class:

public class EvenOddCycler
{
    private readonly string _oddClassName;
    private readonly string _evenClassName;
    private int _numCycles;

    public EvenOddCycler() : this("evenRow","oddRow"){}

    public EvenOddCycler(string evenClassName, string oddClassName)
    {
        _evenClassName = evenClassName;
        _oddClassName = oddClassName;
        _numCycles = 0;
    }

    public string Cycle()
    {
        _numCycles++;
        return IsCurrentlyEven ? _evenClassName : _oddClassName;
    }

    public void Reset()
    {
        _numCycles = 0;
    }

    public bool IsCurrentlyEven
    {
        get { return (_numCycles % 2 == 0); }
    }
}

Really simple stuff here: The constructor takes two strings, one for even and for odd. It has a Cycle function that increases a counter and returns one of the two strings. Also included a Reset() function to re-use the cycler in case you have more than one table on a page.

The usage looks like this:

var cycler = new EvenOddCycler();
foreach (var item in someList)
{
    SomeTable.Rows.Add(SomeFunctionThatReturnsATableRow(item,cycler.Cycle()));
}

What did we gain?

  • No need to constantly repeat the class names over and over again. You shouldn’t hardcode them, but even if you put it in a Resource class of some kind, you still have to have the boolean switch somewhere to get the correct class name. No need here anymore.
  • No need to copy/paste this if you have multiple tables on a Page. Just call cycler.Reset()

Granted. it’s a small thing, but every little thing I don’t have to worry about is good.
This is not Thread-safe because I see no scenario where you would share one cycler. Making it Thread-safe is a simple matter of adding a lock object and locking all three method bodies though.

Disabling Hibernate on Windows 7

Okay, so my system Hard Drive isn’t too big, only 139 GB. But it’s a 10k Raptor, so for the time being it will have to suffice until SSDs get good and affordable.
To my shock, today it was full:

While checking what took so much space, I found quite a few things, but one caught my attention, the Hibernate file:

I have 8 Gigabytes of RAM in my machine, so I have to live with a large Page File (no, I’m not disabling it. If even Mark Russinovich advises against disabling it, that counts more), but I certainly do not need the Hibernate file as this is a Desktop PC that’s either running or off.

Weirdly, I couldn’t find any option in Windows 7 to do so.Not sure if it’s me or if there really is none, but it can easily be done on the command line. Run an elevated command prompt (cmd.exe, Run As Administrator) and type in

powercfg /hibernate off

The Hiberfil.sys should now be gone and the disk space should be free again.

VMWare Player 3 – an awesome and significant update

I remember playing around with VMWare Workstation around 2000 when it was a new idea. It seemed awesome, as I was working in a PC Shop at the time serving both German and English customers. We ran Windows 2000 on our Work machines, but our customers usually had Windows 98 SE or Windows ME. With VMWare, I could install German and English Windows 98 and ME and troubleshooting became a lot easier.

Fast forward to the present day. VMs are a staple in the toolset of every developer now. Need to test your app on Windows XP, Vista and 7? Need to test deployment on both Server 2003 and 2008? Need a legacy Internet Explorer 6 machine? Or want to give Linux a spin? Great, just create a VM. Virtualization products are available for free now, thanks to Microsoft giving away VirtualPC and VMWare giving away their VMWare Server.

Now, VirtualPC is rather useless sadly, as it can not run 64-Bit Guests. Yup, surprised me as well. On the other hand, VMWare Server isn’t really well suited for Desktops – it goes “too deep” into the system and it’s whole interface is more aimed at remote usage (you can’t select an .iso file on your hard drive unless you previously added it to a list of known locations…). There was a different free VMWare product though, VMWare Player.

The first versions could only run, but not create Virtual machines. They were great to run Live Systems/Appliances, for example Mono. Needless to say, soon sites like EasyVMX emerged to allow creating new Virtual Machines, so that Player was a full VM solution. So yesterday I installed the newest VMWare Player 3, already prepared to head to EasyVMX to create my VM. But then I was pleasantly surprised – Player now creates VMs!

And VMWare really went all out here. At least for Windows, they automatically detect your Operating System and offer Easy Install:

Easy Install means that it will do an unattended installation – you only have to enter your Product Key and maybe a password, then you can lean back and wait until it presents you with the Login Screen of a fresh Windows installation.

This, my dear friends, is awesome! I don’t have much use for the advanced features of VMWare Workstation, so Player is my solution of choice – and Player 3 is a significant update!

Well done, VMWare! I wonder if Microsoft will ever catch up? XP Mode in Windows 7 is kinda nice, but I’d rather setup an XP VM in Player than installing a 32-Bit-only Virtual PC.

ASP.net MVC 2 TekPub Video Series Giveaway – The Results

Last week I decided to do a little Giveaway for TekPub’s ASP.net MVC 2 Video Series. If you are an Open Source WebForms developer, you could win one coupon from me and 4 coupons from TekPub.

Now that the Giveaway is over, here are the Winners:

  1. Sean Patterson for HackSaw, a Log4Net Viewer

Yup, that’s it. Only one entry at all. When I wrote “Okay, here is a little experiment. No idea if it works, but one can try” I meant that honestly: I really didn’t know if this would be a success or not. As it turned out, it wasn’t one. But this is science, so I still declare it a success because it yielded results.

I will of course keep my promise, so Sean should receive a coupon today or tomorrow – Have fun with it 🙂

But let’s see what went wrong here. Let’s take the perfectly logical approach here and start with…

Blaming the others
So one theory is that there are simply no Webforms OSS developers. If I would have opened this for PHP developers, I’m fairly certain I would actually have to write a Randomizer to pick 5 Winners. I can’t think of many Webforms OSS projects apart from med-profile 2 blogging engines (SubText and dasBlog). Are there really none? I can’t think of a single high profile one.

Did WebForms miss the push to Open Source?
Between November 2007 and today, Open Source became relatively big in the .net world. Sure, most code is still written internally, but there is a significant amount of mid-to-high profile open source .net applications. But WebForms traditionally lived within companies, due to the relative complexity of deployment. Could it be that WebForms (Which I consider a Legacy Technology, despite all counter-claims by Microsoft) simply missed the OSS train?

I don’t know, but I remember my (abandoned) WebForms Photo Album and how painful deployment was, with manually editing web.config and stuff. It really felt hard to make a WebForms application open source.

Granted, MVC has many of the same problems, but it’s target audience is different. WebForms is a great technology for internal apps where it is more important to have it working quickly rather than achieving an exact look or getting an A Grade in Y!Slow, but I’m guessing the people who use it are not exactly into contributing to OSS. Of course, I am generalizing here, but in my mind there are two Venn Diagrams: One with “WebForms and Open Source” and one with “ASP.net MVC and Open Source”. Both will have overlaps, but I strongly believe that the overlap is much bigger in the MVC one.

That was one part of the experiment, I wanted to find out if there are some high profile projects and hoped to collect some of them in this blog post. The second part of this experiment involves…

Blaming myself
I’ve started blogging again in November 2007, funnily enough with a posting about Open Source and .net. However, I never really tried to build a profile and advertise the blog, also I blog too infrequently. Analytics tells me I have about 100 Visitors a day, which is tiny compared to the high profile blogs. If I am lucky enough to be retweeted or even linked, I might reach 200 Visitors. My all time high is 234 visitors.

However, 2009 saw a more of less steady increase. This is partially due to StackOverflow, which helped me gain a little profile by leading the Scoreboard for quite some time, eventually being the first user to reach 10.000 reputation. Also, my Twitter activity helped me expand my reach a bit. But it is still rather insignificant, because I am not producing enough interesting content yet.

So the part of this experiment was a) to see how many people I can reach and b) see if I can increase my reach.

The Giveaway article is the most read article in the blog, thanks to several ReTweets by people with a much higher profile than me, so there is certainly no reason to believe the article was “buried”.

But still, 484 reads isn’t much. I’ve tried reaching out to some websites and podcasts for a mention, but that was badly planned. Many podcasts are produced weeks in advance, so I should have thought about that before. And many Websites want real money for their ad space, which is perfectly understandable (but I still had to try asking them, right?)

Goals for 2010 and future Giveaways
So what did I learn out of this experiment? I learned that my reach isn’t there yet. But I also learned that I can increase my reach by posting more and making sure my posts get mentioned (For more exciting and totally unexpected statements, read this article).

This is logical, but it was worth for me experimenting just what works best, and I found that RTs and Mentions on Twitter are about 10 times more useful than Pingbacks/Links in other people’s blog comments. This is not surprising – when I read a blog posting, I never read the “300 Pingbacks for this article” section, and I rarely read more than 10 or so comments. But on Twitter, I click on many links simply because they are “fed” to me.

Overall, I do not feel that I just wasted $28. I knew it was an experiment, I wanted to know my reach, and now I know it.

So I’ll increase my blogging output during 2010, Sean will have fun with the MVC 2 Videos, WebForms will always be a legacy technology in my eyes, and I do plan another Giveaway for another series sometime this year, although with proper preparation this time.

Move on people, nothing more to see here 🙂

Extension Method: Return another string if string is null or empty

Just a tiny little extension method. You may know the ?? operator for null checks:

var something = objectThatCouldBeNull ?? anotherObject;

Basically if objectThatCouldBeNull is null, then anotherObject is used instead. Unfortunately, this does not work with empty strings, so here is a quick extension method for that:

public static string IfEmpty(this string input, string otherString)
{
    if (string.IsNullOrEmpty(input)) return otherString;
    return input;
}

call it like

var myString = someString.IfEmpty("someOtherString");

The nice thing on extension methods is that you can call them on null instances of an object, so a call to

((string)null).IfEmpty("SomethingElse");

is safe.

A simple bracket matcher for JavaScript

jQuery is a great Framework, but it can be a bit overwhelming in terms of nesting, especially when working with sub-par JavaScript editors like Visual Studio. When working with jQuery, you may end up with many open brackets { that need closing brackets }, but then there are also open brackets like ({ that need }). So when you are at the end of a file that has a bunch of closing brackets, it can be painful to try to figure out if all of them are properly closed.

I hacked together a simple Bracket Matcher tool that takes a JavaScript text and highlights the blocks:
Bracket Matcher

It is not finished – }) end tags are not properly highlighted (even though ({ are) and the “expectedEndTag” part that should automatically tell me if my blocks are properly closed doesn’t work. Reason is simply that the current version was good enough to solve a problem at hand, so I might improve it later.

Create a simple WinForms application with a TextBox (MultiLine) and a RichTextEdit (WordWrap = false) and wire it up like this:

private void button1_Click(object sender, EventArgs e)
{
    richTextBox1.Clear();
    string input = textBox1.Text.Replace("\r\n", "\n");

    var expectedEndTokens = new Stack<string>();
    richTextBox1.SelectionColor = Color.Black;
    for (int i = 0; i < input.Length; i++)
    {
        char cprev = (i > 0) ? input[i - 1] : char.MinValue;
        char c = input[i];
        char cnext = (i < input.Length - 1) ? input[i + 1] : char.MinValue;
        if (c == '{')
        {
            ColorChooser.SelectNextColor();
            richTextBox1.Select(richTextBox1.Text.Length - 1, 1);
            richTextBox1.SelectionColor = ColorChooser.Color;
            if (cprev == '(')
            {
                expectedEndTokens.Push("})");
            }
            else
            {
                expectedEndTokens.Push("}");
            }
        }

        if (cprev == '\n') richTextBox1.SelectionColor = ColorChooser.Color;

        richTextBox1.AppendText(c.ToString());

        if (c == '}')
        {
            ColorChooser.SelectPreviousColor();
            richTextBox1.SelectionColor = ColorChooser.Color;
        }
    }
}

private static class ColorChooser
{
   // Change these to whatever you prefer, add or remove if you want
    private static readonly Color[] HighlightColors = new Color[]{
        Color.Blue,
        Color.DarkGreen,
        Color.Violet,
        Color.Red,
        Color.DarkCyan,
        Color.Gray,
        Color.Magenta
    };

    private static int currentColorIndex = 0;

    public static Color Color
    {
        get
        {
            return HighlightColors[currentColorIndex];
        }
    }

    public static void SelectNextColor()
    {
        lock (HighlightColors)
        {
            currentColorIndex++;
            if (currentColorIndex >= HighlightColors.Length) currentColorIndex = 0;
        }
    }

    public static void SelectPreviousColor()
    {
        lock (HighlightColors)
        {
            currentColorIndex--;
            if (currentColorIndex < 0) currentColorIndex = HighlightColors.Length-1;
        }
    }
}